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/abaddon.cpp | 6 +- src/abaddon.hpp | 2 +- src/components/chatinput.cpp | 204 ++++++++++++++++++++++++++++++++++++++++-- src/components/chatinput.hpp | 89 +++++++++++++++++- src/components/chatlist.cpp | 4 - src/components/chatlist.hpp | 3 - src/components/chatwindow.cpp | 11 ++- src/components/chatwindow.hpp | 4 +- src/constants.hpp | 1 + src/discord/discord.cpp | 55 +++++++----- src/discord/discord.hpp | 7 +- src/discord/httpclient.cpp | 19 ++++ src/discord/httpclient.hpp | 3 + src/http.cpp | 45 +++++++++- src/http.hpp | 15 +++- 15 files changed, 416 insertions(+), 52 deletions(-) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 2e8ecaa..05e1942 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -743,7 +743,7 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) { }); } -void Abaddon::ActionChatInputSubmit(std::string msg, Snowflake channel, Snowflake referenced_message) { +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 @@ -751,9 +751,9 @@ void Abaddon::ActionChatInputSubmit(std::string msg, Snowflake channel, Snowflak if (!m_discord.HasChannelPermission(m_discord.GetUserData().ID, channel, Permission::VIEW_CHANNEL)) return; if (referenced_message.IsValid()) - m_discord.SendChatMessage(msg, channel, referenced_message); + m_discord.SendChatMessage(msg, attachment_paths, channel, referenced_message); else - m_discord.SendChatMessage(msg, channel); + m_discord.SendChatMessage(msg, attachment_paths, channel); } void Abaddon::ActionChatEditMessage(Snowflake channel_id, Snowflake id) { diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 3296c45..9efb891 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, Snowflake channel, Snowflake referenced_message); + void ActionChatInputSubmit(std::string msg, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message); 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 c3eca32..6afdd82 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -1,6 +1,10 @@ #include "chatinput.hpp" +#include "abaddon.hpp" +#include "constants.hpp" +#include +#include -ChatInput::ChatInput() { +ChatInputText::ChatInputText() { get_style_context()->add_class("message-input"); set_propagate_natural_height(true); set_min_content_height(20); @@ -20,22 +24,26 @@ ChatInput::ChatInput() { add(m_textview); } -void ChatInput::InsertText(const Glib::ustring &text) { +void ChatInputText::InsertText(const Glib::ustring &text) { GetBuffer()->insert_at_cursor(text); m_textview.grab_focus(); } -Glib::RefPtr ChatInput::GetBuffer() { +Glib::RefPtr ChatInputText::GetBuffer() { return m_textview.get_buffer(); } // this isnt connected directly so that the chat window can handle stuff like the completer first -bool ChatInput::ProcessKeyPress(GdkEventKey *event) { +bool ChatInputText::ProcessKeyPress(GdkEventKey *event) { if (event->keyval == GDK_KEY_Escape) { m_signal_escape.emit(); return true; } + if ((event->state & GDK_CONTROL_MASK) && event->keyval == GDK_KEY_v) { + return CheckHandleClipboardPaste(); + } + if (event->keyval == GDK_KEY_Return) { if (event->state & GDK_SHIFT_MASK) return false; @@ -53,10 +61,196 @@ bool ChatInput::ProcessKeyPress(GdkEventKey *event) { return false; } -void ChatInput::on_grab_focus() { +void ChatInputText::on_grab_focus() { m_textview.grab_focus(); } +bool ChatInputText::CheckHandleClipboardPaste() { + auto clip = Gtk::Clipboard::get(); + if (!clip->wait_is_image_available()) return false; + + const auto pb = clip->wait_for_image(); + std::array dest_name {}; + if (std::tmpnam(dest_name.data()) == nullptr) { + fprintf(stderr, "failed to get temporary path\n"); + return true; + } + + // stinky + std::filesystem::path part1(std::filesystem::temp_directory_path() / "abaddon-cache"); + std::filesystem::path part2(dest_name.data()); + const auto dest_path = (part1 / part2.relative_path()).string(); + + try { + pb->save(dest_path, "png"); + } catch (...) { + fprintf(stderr, "pasted image save error\n"); + return true; + } + + m_signal_image_paste.emit(pb, dest_path); + + return true; +} + +ChatInputText::type_signal_submit ChatInputText::signal_submit() { + return m_signal_submit; +} + +ChatInputText::type_signal_escape ChatInputText::signal_escape() { + return m_signal_escape; +} + +ChatInputText::type_signal_image_paste ChatInputText::signal_image_paste() { + return m_signal_image_paste; +} + +ChatInputAttachmentContainer::ChatInputAttachmentContainer() + : m_box(Gtk::ORIENTATION_HORIZONTAL) { + get_style_context()->add_class("attachment-container"); + + add(m_box); + m_box.show(); + + set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_NEVER); + set_vexpand(true); + set_size_request(-1, AttachmentItemSize + 10); +} + +void ChatInputAttachmentContainer::Clear() { + for (auto *x : m_attachments) + delete x; + m_attachments.clear(); +} + +bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb, const std::string &path) { + if (m_attachments.size() == 10) return false; + + auto *item = Gtk::make_managed(path, pb); + item->show(); + item->set_valign(Gtk::ALIGN_CENTER); + m_box.add(*item); + + m_attachments.insert(item); + + item->signal_remove().connect([this, item] { + m_attachments.erase(item); + delete item; + if (m_attachments.empty()) + m_signal_emptied.emit(); + }); + + return true; +} + +std::vector ChatInputAttachmentContainer::GetFilePaths() const { + std::vector ret; + for (auto *x : m_attachments) + ret.push_back(x->GetPath()); + return ret; +} + +ChatInputAttachmentContainer::type_signal_emptied ChatInputAttachmentContainer::signal_emptied() { + return m_signal_emptied; +} + +ChatInputAttachmentItem::ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb) + : m_path(std::move(path)) + , m_img(Gtk::make_managed()) { + get_style_context()->add_class("attachment-item"); + + int outw, outh; + GetImageDimensions(pb->get_width(), pb->get_height(), outw, outh, AttachmentItemSize, AttachmentItemSize); + m_img->property_pixbuf() = pb->scale_simple(outw, outh, Gdk::INTERP_BILINEAR); + + set_size_request(AttachmentItemSize, AttachmentItemSize); + m_box.add(*m_img); + add(m_box); + show_all_children(); + + SetupMenu(); +} + +std::string ChatInputAttachmentItem::GetPath() const { + return m_path; +} + +void ChatInputAttachmentItem::SetupMenu() { + m_menu_remove.set_label("Remove"); + m_menu_remove.signal_activate().connect([this] { + m_signal_remove.emit(); + }); + + m_menu.add(m_menu_remove); + m_menu.show_all(); + + signal_button_press_event().connect([this](GdkEventButton *ev) -> bool { + if (ev->button == GDK_BUTTON_SECONDARY) { + m_menu.popup_at_pointer(reinterpret_cast(ev)); + return true; + } + + return false; + }); +} + +ChatInputAttachmentItem::type_signal_remove ChatInputAttachmentItem::signal_remove() { + return m_signal_remove; +} + +ChatInput::ChatInput() + : Gtk::Box(Gtk::ORIENTATION_VERTICAL) { + m_input.signal_escape().connect([this] { + m_attachments.Clear(); + m_attachments_revealer.set_reveal_child(false); + 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); + if (b) { + m_attachments.Clear(); + m_attachments_revealer.set_reveal_child(false); + } + return b; + }); + + m_attachments.set_vexpand(false); + + m_attachments_revealer.set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP); + m_attachments_revealer.add(m_attachments); + add(m_attachments_revealer); + add(m_input); + show_all_children(); + + m_input.signal_image_paste().connect([this](const Glib::RefPtr &pb, const std::string &path) { + if (m_attachments.AddImage(pb, path)) + m_attachments_revealer.set_reveal_child(true); + }); + + // double hack ! + auto cb = [this](GdkEventKey *e) -> bool { + return event(reinterpret_cast(e)); + }; + m_input.signal_key_press_event().connect(cb, false); + + m_attachments.signal_emptied().connect([this] { + m_attachments_revealer.set_reveal_child(false); + }); +} + +void ChatInput::InsertText(const Glib::ustring &text) { + m_input.InsertText(text); +} + +Glib::RefPtr ChatInput::GetBuffer() { + return m_input.GetBuffer(); +} + +bool ChatInput::ProcessKeyPress(GdkEventKey *event) { + return m_input.ProcessKeyPress(event); +} + ChatInput::type_signal_submit ChatInput::signal_submit() { return m_signal_submit; } diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index ad7f0b1..a6b2a31 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -1,9 +1,57 @@ #pragma once #include -class ChatInput : public Gtk::ScrolledWindow { +class ChatInputAttachmentItem : public Gtk::EventBox { public: - ChatInput(); + ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb); + + [[nodiscard]] std::string GetPath() const; + +private: + void SetupMenu(); + + Gtk::Menu m_menu; + Gtk::MenuItem m_menu_remove; + + Gtk::Box m_box; + Gtk::Image *m_img = nullptr; + + std::string m_path; + +private: + using type_signal_remove = sigc::signal; + + type_signal_remove m_signal_remove; + +public: + type_signal_remove signal_remove(); +}; + +class ChatInputAttachmentContainer : public Gtk::ScrolledWindow { +public: + ChatInputAttachmentContainer(); + + void Clear(); + bool AddImage(const Glib::RefPtr &pb, const std::string &path); + [[nodiscard]] std::vector GetFilePaths() const; + +private: + std::set m_attachments; + + Gtk::Box m_box; + +private: + using type_signal_emptied = sigc::signal; + + type_signal_emptied m_signal_emptied; + +public: + type_signal_emptied signal_emptied(); +}; + +class ChatInputText : public Gtk::ScrolledWindow { +public: + ChatInputText(); void InsertText(const Glib::ustring &text); Glib::RefPtr GetBuffer(); @@ -15,9 +63,42 @@ protected: private: Gtk::TextView m_textview; + bool CheckHandleClipboardPaste(); + void HandleNewPastedImage(const Glib::RefPtr &pb, const std::string &filename); + +public: + using type_signal_submit = sigc::signal; + using type_signal_escape = sigc::signal; + using type_signal_image_paste = sigc::signal, std::string>; + + type_signal_submit signal_submit(); + type_signal_escape signal_escape(); + type_signal_image_paste signal_image_paste(); + +private: + type_signal_submit m_signal_submit; + type_signal_escape m_signal_escape; + type_signal_image_paste m_signal_image_paste; +}; + +class ChatInput : public Gtk::Box { +public: + ChatInput(); + + void InsertText(const Glib::ustring &text); + Glib::RefPtr GetBuffer(); + bool ProcessKeyPress(GdkEventKey *event); + +private: + Gtk::Revealer m_attachments_revealer; + ChatInputAttachmentContainer m_attachments; + ChatInputText m_input; + public: - typedef sigc::signal type_signal_submit; - typedef sigc::signal type_signal_escape; + // 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_escape = sigc::signal; type_signal_submit signal_submit(); type_signal_escape signal_escape(); diff --git a/src/components/chatlist.cpp b/src/components/chatlist.cpp index 5b923b5..9bd8daf 100644 --- a/src/components/chatlist.cpp +++ b/src/components/chatlist.cpp @@ -352,10 +352,6 @@ ChatList::type_signal_action_message_edit ChatList::signal_action_message_edit() return m_signal_action_message_edit; } -ChatList::type_signal_action_chat_submit ChatList::signal_action_chat_submit() { - return m_signal_action_chat_submit; -} - ChatList::type_signal_action_chat_load_history ChatList::signal_action_chat_load_history() { return m_signal_action_chat_load_history; } diff --git a/src/components/chatlist.hpp b/src/components/chatlist.hpp index f77bbd6..9cc6992 100644 --- a/src/components/chatlist.hpp +++ b/src/components/chatlist.hpp @@ -63,7 +63,6 @@ private: public: // these are all forwarded by the parent using type_signal_action_message_edit = sigc::signal; - 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; @@ -73,7 +72,6 @@ public: using type_signal_action_reply_to = sigc::signal; type_signal_action_message_edit signal_action_message_edit(); - type_signal_action_chat_submit signal_action_chat_submit(); type_signal_action_chat_load_history signal_action_chat_load_history(); type_signal_action_channel_click signal_action_channel_click(); type_signal_action_insert_mention signal_action_insert_mention(); @@ -84,7 +82,6 @@ public: private: type_signal_action_message_edit m_signal_action_message_edit; - type_signal_action_chat_submit m_signal_action_chat_submit; type_signal_action_chat_load_history m_signal_action_chat_load_history; type_signal_action_channel_click m_signal_action_channel_click; type_signal_action_insert_mention m_signal_action_insert_mention; diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 5aab4e6..46c34d4 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -45,6 +45,8 @@ ChatWindow::ChatWindow() { m_topic_text.set_halign(Gtk::ALIGN_START); m_topic_text.show(); + m_input->set_valign(Gtk::ALIGN_END); + m_input->signal_submit().connect(sigc::mem_fun(*this, &ChatWindow::OnInputSubmit)); m_input->signal_escape().connect([this]() { if (m_is_replying) @@ -70,9 +72,6 @@ ChatWindow::ChatWindow() { m_chat->signal_action_chat_load_history().connect([this](Snowflake id) { m_signal_action_chat_load_history.emit(id); }); - m_chat->signal_action_chat_submit().connect([this](const std::string &str, Snowflake channel_id, Snowflake referenced_id) { - m_signal_action_chat_submit.emit(str, channel_id, referenced_id); - }); m_chat->signal_action_insert_mention().connect([this](Snowflake id) { // lowkey gross m_signal_action_insert_mention.emit(id); @@ -210,15 +209,15 @@ Snowflake ChatWindow::GetActiveChannel() const { return m_active_channel; } -bool ChatWindow::OnInputSubmit(const Glib::ustring &text) { +bool ChatWindow::OnInputSubmit(const Glib::ustring &text, const std::vector &attachment_paths) { if (!m_rate_limit_indicator->CanSpeak()) return false; - if (text.empty()) + if (text.empty() && attachment_paths.empty()) return false; if (m_active_channel.IsValid()) - m_signal_action_chat_submit.emit(text, m_active_channel, m_replying_to); // m_replying_to is checked for invalid in the handler + 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 if (m_is_replying) StopReplying(); diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index 9b27ff1..ab0bee1 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -55,7 +55,7 @@ protected: Snowflake m_active_channel; - bool OnInputSubmit(const Glib::ustring &text); + bool OnInputSubmit(const Glib::ustring &text, const std::vector &attachment_paths); bool OnKeyPressEvent(GdkEventKey *e); void OnScrollEdgeOvershot(Gtk::PositionType pos); @@ -84,7 +84,7 @@ protected: public: using type_signal_action_message_edit = sigc::signal; - using type_signal_action_chat_submit = sigc::signal; + using type_signal_action_chat_submit = sigc::signal, Snowflake, Snowflake>; 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/constants.hpp b/src/constants.hpp index 6c6276f..30de2ae 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -2,3 +2,4 @@ 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 = 128; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index d94f3df..151d88c 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -424,37 +424,34 @@ void DiscordClient::ChatMessageCallback(const std::string &nonce, const http::re } } -void DiscordClient::SendChatMessage(const std::string &content, Snowflake channel) { - // @([^@#]{1,32})#(\\d{4}) +void DiscordClient::SendChatMessageAttachments(const std::string &content, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message) { const auto nonce = std::to_string(Snowflake::FromNow()); CreateMessageObject obj; obj.Content = content; obj.Nonce = nonce; - 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 - Message tmp; - tmp.Content = content; - tmp.ID = nonce; - tmp.ChannelID = channel; - 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.IsPending = true; - m_store.SetMessage(tmp.ID, tmp); - m_signal_message_sent.emit(tmp); + if (referenced_message.IsValid()) + obj.MessageReference.emplace().MessageID = referenced_message; + + 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), sigc::bind<0>(sigc::mem_fun(*this, &DiscordClient::ChatMessageCallback), nonce)); } -void DiscordClient::SendChatMessage(const std::string &content, Snowflake channel, Snowflake referenced_message) { +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; - obj.MessageReference.emplace().MessageID = referenced_message; + 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 Message tmp; tmp.Content = content; tmp.ID = nonce; @@ -471,6 +468,24 @@ void DiscordClient::SendChatMessage(const std::string &content, Snowflake channe m_signal_message_sent.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::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::DeleteMessage(Snowflake channel_id, Snowflake id) { std::string path = "/channels/" + std::to_string(channel_id) + "/messages/" + std::to_string(id); m_http.MakeDELETE(path, [](auto) {}); 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(); diff --git a/src/discord/httpclient.cpp b/src/discord/httpclient.cpp index f5b640c..6646bf3 100644 --- a/src/discord/httpclient.cpp +++ b/src/discord/httpclient.cpp @@ -112,6 +112,25 @@ void HTTPClient::MakeGET(const std::string &path, const std::function &cb) { + printf("%s %s\n", req.get_method(), req.get_url().c_str()); + m_futures.push_back(std::async(std::launch::async, [this, cb, req = std::move(req)]() mutable { + auto res = req.execute(); + OnResponse(res, cb); + })); +} + void HTTPClient::CleanupFutures() { for (auto it = m_futures.begin(); it != m_futures.end();) { if (it->wait_for(std::chrono::seconds(0)) == std::future_status::ready) diff --git a/src/discord/httpclient.hpp b/src/discord/httpclient.hpp index 47b8a4d..366d4eb 100644 --- a/src/discord/httpclient.hpp +++ b/src/discord/httpclient.hpp @@ -23,6 +23,9 @@ public: void MakePOST(const std::string &path, const std::string &payload, const std::function &cb); void MakePUT(const std::string &path, const std::string &payload, const std::function &cb); + [[nodiscard]] http::request CreateRequest(http::EMethod method, std::string path); + void Execute(http::request &&req, const std::function &cb); + private: void OnResponse(const http::response_type &r, const std::function &cb); void CleanupFutures(); diff --git a/src/http.cpp b/src/http.cpp index 7338d39..beb1944 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -29,12 +29,33 @@ request::request(EMethod method, std::string url) prepare(); } +request::request(request &&other) noexcept + : m_curl(std::exchange(other.m_curl, nullptr)) + , m_url(std::exchange(other.m_url, "")) + , m_method(std::exchange(other.m_method, nullptr)) + , 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)) { + // i think this is correct??? +} + request::~request() { if (m_curl != nullptr) curl_easy_cleanup(m_curl); if (m_header_list != nullptr) curl_slist_free_all(m_header_list); + + if (m_form != nullptr) + curl_mime_free(m_form); +} + +const std::string &request::get_url() const { + return m_url; +} + +const char *request::get_method() const { + return m_method; } void request::set_verify_ssl(bool verify) { @@ -57,6 +78,26 @@ void request::set_user_agent(const std::string &data) { curl_easy_setopt(m_curl, CURLOPT_USERAGENT, data.c_str()); } +void request::make_form() { + m_form = curl_mime_init(m_curl); +} + +// file must exist until request completes +void request::add_file(std::string_view name, std::string_view file_path, std::string_view filename) { + auto *field = curl_mime_addpart(m_form); + curl_mime_name(field, name.data()); + curl_mime_filedata(field, file_path.data()); + curl_mime_filename(field, filename.data()); +} + +// copied +void request::add_field(std::string_view name, const char *data, size_t size) { + puts(name.data()); + auto *field = curl_mime_addpart(m_form); + curl_mime_name(field, name.data()); + curl_mime_data(field, data, size); +} + response request::execute() { if (m_curl == nullptr) { auto response = detail::make_response(m_url, EStatusCode::ClientErrorCURLInit); @@ -76,12 +117,14 @@ response request::execute() { m_error_buf[0] = '\0'; if (m_header_list != nullptr) curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_header_list); + if (m_form != nullptr) + curl_easy_setopt(m_curl, CURLOPT_MIMEPOST, m_form); CURLcode result = curl_easy_perform(m_curl); if (result != CURLE_OK) { auto response = detail::make_response(m_url, EStatusCode::ClientErrorCURLPerform); response.error_string = curl_easy_strerror(result); - response.error_string += " " + std::string(m_error_buf); + response.error_string += " " + std::string(m_error_buf.data()); return response; } diff --git a/src/http.hpp b/src/http.hpp index 4220a6e..0c570db 100644 --- a/src/http.hpp +++ b/src/http.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include @@ -98,13 +99,24 @@ struct response { struct request { request(EMethod method, std::string url); + request(request &&other) noexcept; ~request(); + request(const request &) = delete; + request &operator=(const request &) = delete; + request &operator=(request &&) noexcept = delete; + + const std::string &get_url() const; + const char *get_method() const; + void set_verify_ssl(bool verify); void set_proxy(const std::string &proxy); void set_header(const std::string &name, const std::string &value); void set_body(const std::string &data); void set_user_agent(const std::string &data); + void make_form(); + void add_file(std::string_view name, std::string_view file_path, std::string_view filename); + void add_field(std::string_view name, const char *data, size_t size); response execute(); @@ -115,7 +127,8 @@ private: std::string m_url; const char *m_method; curl_slist *m_header_list = nullptr; - char m_error_buf[CURL_ERROR_SIZE] = { 0 }; + std::array m_error_buf = { 0 }; + curl_mime *m_form = nullptr; }; using response_type = response; -- cgit v1.2.3 From c6182e8923026108957227b41249294abbd85f79 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 6 Jun 2022 03:55:25 -0400 Subject: only save temporary image when theres room in container --- src/components/chatinput.cpp | 45 +++++++++++++++++++++++--------------------- src/components/chatinput.hpp | 5 ++--- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 6afdd82..94bca22 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -70,27 +70,13 @@ bool ChatInputText::CheckHandleClipboardPaste() { if (!clip->wait_is_image_available()) return false; const auto pb = clip->wait_for_image(); - std::array dest_name {}; - if (std::tmpnam(dest_name.data()) == nullptr) { - fprintf(stderr, "failed to get temporary path\n"); - return true; - } - - // stinky - std::filesystem::path part1(std::filesystem::temp_directory_path() / "abaddon-cache"); - std::filesystem::path part2(dest_name.data()); - const auto dest_path = (part1 / part2.relative_path()).string(); + if (pb) { + m_signal_image_paste.emit(pb); - try { - pb->save(dest_path, "png"); - } catch (...) { - fprintf(stderr, "pasted image save error\n"); return true; + } else { + return false; } - - m_signal_image_paste.emit(pb, dest_path); - - return true; } ChatInputText::type_signal_submit ChatInputText::signal_submit() { @@ -123,9 +109,26 @@ void ChatInputAttachmentContainer::Clear() { m_attachments.clear(); } -bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb, const std::string &path) { +bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) { if (m_attachments.size() == 10) return false; + std::array dest_name {}; + if (std::tmpnam(dest_name.data()) == nullptr) { + fprintf(stderr, "failed to get temporary path\n"); + return false; + } + + std::filesystem::path part1(std::filesystem::temp_directory_path() / "abaddon-cache"); + std::filesystem::path part2(dest_name.data()); + const auto path = (part1 / part2.relative_path()).string(); + + try { + pb->save(path, "png"); + } catch (...) { + fprintf(stderr, "pasted image save error\n"); + return false; + } + auto *item = Gtk::make_managed(path, pb); item->show(); item->set_valign(Gtk::ALIGN_CENTER); @@ -223,8 +226,8 @@ ChatInput::ChatInput() add(m_input); show_all_children(); - m_input.signal_image_paste().connect([this](const Glib::RefPtr &pb, const std::string &path) { - if (m_attachments.AddImage(pb, path)) + m_input.signal_image_paste().connect([this](const Glib::RefPtr &pb) { + if (m_attachments.AddImage(pb)) m_attachments_revealer.set_reveal_child(true); }); diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index a6b2a31..83fde95 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -32,7 +32,7 @@ public: ChatInputAttachmentContainer(); void Clear(); - bool AddImage(const Glib::RefPtr &pb, const std::string &path); + bool AddImage(const Glib::RefPtr &pb); [[nodiscard]] std::vector GetFilePaths() const; private: @@ -64,12 +64,11 @@ private: Gtk::TextView m_textview; bool CheckHandleClipboardPaste(); - void HandleNewPastedImage(const Glib::RefPtr &pb, const std::string &filename); public: using type_signal_submit = sigc::signal; using type_signal_escape = sigc::signal; - using type_signal_image_paste = sigc::signal, std::string>; + using type_signal_image_paste = sigc::signal>; type_signal_submit signal_submit(); type_signal_escape signal_escape(); -- cgit v1.2.3 From d7177cac979a376dd7ea6dd46f5c5edbb68a06d8 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 6 Jun 2022 03:59:20 -0400 Subject: remove temporary image file when attachment removed --- src/components/chatinput.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 94bca22..7597bbe 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -104,8 +104,11 @@ ChatInputAttachmentContainer::ChatInputAttachmentContainer() } void ChatInputAttachmentContainer::Clear() { - for (auto *x : m_attachments) - delete x; + for (auto *item : m_attachments) { + std::error_code ec; + std::filesystem::remove(item->GetPath(), ec); + delete item; + } m_attachments.clear(); } @@ -137,6 +140,8 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) m_attachments.insert(item); item->signal_remove().connect([this, item] { + std::error_code ec; + std::filesystem::remove(item->GetPath(), ec); m_attachments.erase(item); delete item; if (m_attachments.empty()) -- 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(-) 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 5cf4d8e160dd5bb2a8681106c030ecee152c7366 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 10 Jun 2022 01:28:27 -0400 Subject: dont use tmpnam because its stupid --- src/components/chatinput.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 166d04f..f9bbce4 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -122,15 +122,9 @@ void ChatInputAttachmentContainer::ClearNoPurge() { bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) { if (m_attachments.size() == 10) return false; - std::array dest_name {}; - if (std::tmpnam(dest_name.data()) == nullptr) { - fprintf(stderr, "failed to get temporary path\n"); - return false; - } - - std::filesystem::path part1(std::filesystem::temp_directory_path() / "abaddon-cache"); - std::filesystem::path part2(dest_name.data()); - const auto path = (part1 / part2.relative_path()).string(); + static unsigned go_up = 0; + std::string dest_name = "pasted-image-" + std::to_string(go_up++); + const auto path = (std::filesystem::temp_directory_path() / "abaddon-cache" / dest_name).string(); try { pb->save(path, "png"); -- 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 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 d0fa308f6e339b94044d39bf0e76b8221da48c3a Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 16 Jun 2022 01:09:54 -0400 Subject: preserve attachment insertion order --- src/components/chatinput.cpp | 5 +++-- src/components/chatinput.hpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 2b8b5a5..5a6e71e 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -138,12 +138,13 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) item->set_valign(Gtk::ALIGN_CENTER); m_box.add(*item); - m_attachments.insert(item); + m_attachments.push_back(item); item->signal_remove().connect([this, item] { std::error_code ec; std::filesystem::remove(item->GetPath(), ec); - m_attachments.erase(item); + if (auto it = std::find(m_attachments.begin(), m_attachments.end(), item); it != m_attachments.end()) + m_attachments.erase(it); delete item; if (m_attachments.empty()) m_signal_emptied.emit(); diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index ba3ab36..254f96f 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -41,7 +41,7 @@ public: [[nodiscard]] std::vector GetAttachments() const; private: - std::set m_attachments; + std::vector m_attachments; Gtk::Box m_box; -- cgit v1.2.3 From 4ee7025ab09b606a2556bf9f42c1218d7fd72843 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 17 Jun 2022 02:46:55 -0400 Subject: add file upload via dnd + rework http --- src/abaddon.cpp | 2 +- src/components/chatinput.cpp | 92 +++++++++++++++++++++++++++++++++------- src/components/chatinput.hpp | 19 ++++++--- src/components/chatwindow.cpp | 4 ++ src/components/chatwindow.hpp | 1 + src/discord/chatsubmitparams.hpp | 4 +- src/discord/discord.cpp | 5 +-- src/http.cpp | 24 +++++++++-- src/http.hpp | 8 ++-- src/windows/mainwindow.cpp | 17 ++++++++ src/windows/mainwindow.hpp | 3 ++ 11 files changed, 146 insertions(+), 33 deletions(-) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 28b6262..cfbf6b8 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -746,7 +746,7 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) { 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()); + puts(attachment.File->get_path().c_str()); } } diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 5a6e71e..269a2ac 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -105,8 +105,7 @@ ChatInputAttachmentContainer::ChatInputAttachmentContainer() void ChatInputAttachmentContainer::Clear() { for (auto *item : m_attachments) { - std::error_code ec; - std::filesystem::remove(item->GetPath(), ec); + item->RemoveIfTemp(); delete item; } m_attachments.clear(); @@ -133,16 +132,36 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) return false; } - auto *item = Gtk::make_managed(path, pb); + auto *item = Gtk::make_managed(Gio::File::create_for_path(path), pb); item->show(); item->set_valign(Gtk::ALIGN_CENTER); m_box.add(*item); m_attachments.push_back(item); - item->signal_remove().connect([this, item] { - std::error_code ec; - std::filesystem::remove(item->GetPath(), ec); + item->signal_item_removed().connect([this, item] { + item->RemoveIfTemp(); + if (auto it = std::find(m_attachments.begin(), m_attachments.end(), item); it != m_attachments.end()) + m_attachments.erase(it); + delete item; + if (m_attachments.empty()) + m_signal_emptied.emit(); + }); + + return true; +} + +bool ChatInputAttachmentContainer::AddFile(const Glib::RefPtr &file) { + if (m_attachments.size() == 10) return false; + + auto *item = Gtk::make_managed(file); + item->show(); + item->set_valign(Gtk::ALIGN_CENTER); + m_box.add(*item); + + m_attachments.push_back(item); + + item->signal_item_removed().connect([this, item] { if (auto it = std::find(m_attachments.begin(), m_attachments.end(), item); it != m_attachments.end()) m_attachments.erase(it); delete item; @@ -155,8 +174,11 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) std::vector ChatInputAttachmentContainer::GetAttachments() const { std::vector ret; - for (auto *x : m_attachments) - ret.push_back({ x->GetPath(), x->GetType() }); + for (auto *x : m_attachments) { + if (!x->GetFile()->query_exists()) + puts("bad!"); + ret.push_back({ x->GetFile(), x->GetType(), x->GetFilename() }); + } return ret; } @@ -164,10 +186,28 @@ ChatInputAttachmentContainer::type_signal_emptied ChatInputAttachmentContainer:: return m_signal_emptied; } -ChatInputAttachmentItem::ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb) - : m_path(std::move(path)) +ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr &file) + : m_file(file) + , m_img(Gtk::make_managed(Abaddon::Get().GetImageManager().GetPlaceholder(AttachmentItemSize))) + , m_type(ChatSubmitParams::ExtantFile) { + get_style_context()->add_class("attachment-item"); + + set_size_request(AttachmentItemSize, AttachmentItemSize); + m_box.add(*m_img); + add(m_box); + show_all_children(); + + SetupMenu(); + + auto info = m_file->query_info(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); + m_filename = info->get_attribute_string(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); +} + +ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb) + : m_file(file) , m_img(Gtk::make_managed()) - , m_type(ChatSubmitParams::PastedImage) { + , m_type(ChatSubmitParams::PastedImage) + , m_filename("unknown.png") { get_style_context()->add_class("attachment-item"); int outw, outh; @@ -182,18 +222,31 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(std::string path, const Glib::R SetupMenu(); } -std::string ChatInputAttachmentItem::GetPath() const { - return m_path; +Glib::RefPtr ChatInputAttachmentItem::GetFile() const { + return m_file; } ChatSubmitParams::AttachmentType ChatInputAttachmentItem::GetType() const { return m_type; } +std::string ChatInputAttachmentItem::GetFilename() const { + return m_filename; +} + +bool ChatInputAttachmentItem::IsTemp() const noexcept { + return m_type == ChatSubmitParams::PastedImage; +} + +void ChatInputAttachmentItem::RemoveIfTemp() { + if (IsTemp()) + m_file->remove(); +} + void ChatInputAttachmentItem::SetupMenu() { m_menu_remove.set_label("Remove"); m_menu_remove.signal_activate().connect([this] { - m_signal_remove.emit(); + m_signal_item_removed.emit(); }); m_menu.add(m_menu_remove); @@ -209,8 +262,8 @@ void ChatInputAttachmentItem::SetupMenu() { }); } -ChatInputAttachmentItem::type_signal_remove ChatInputAttachmentItem::signal_remove() { - return m_signal_remove; +ChatInputAttachmentItem::type_signal_item_removed ChatInputAttachmentItem::signal_item_removed() { + return m_signal_item_removed; } ChatInput::ChatInput() @@ -271,6 +324,13 @@ bool ChatInput::ProcessKeyPress(GdkEventKey *event) { return m_input.ProcessKeyPress(event); } +void ChatInput::AddAttachment(const Glib::RefPtr &file) { + const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); + + if (can_attach_files && m_attachments.AddFile(file)) + m_attachments_revealer.set_reveal_child(true); +} + ChatInput::type_signal_submit ChatInput::signal_submit() { return m_signal_submit; } diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index 254f96f..865b23e 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -5,10 +5,14 @@ class ChatInputAttachmentItem : public Gtk::EventBox { public: - ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb); + ChatInputAttachmentItem(const Glib::RefPtr &file); + ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb); - [[nodiscard]] std::string GetPath() const; + [[nodiscard]] Glib::RefPtr GetFile() const; [[nodiscard]] ChatSubmitParams::AttachmentType GetType() const; + [[nodiscard]] std::string GetFilename() const; + [[nodiscard]] bool IsTemp() const noexcept; + void RemoveIfTemp(); private: void SetupMenu(); @@ -19,16 +23,17 @@ private: Gtk::Box m_box; Gtk::Image *m_img = nullptr; - std::string m_path; + Glib::RefPtr m_file; ChatSubmitParams::AttachmentType m_type; + std::string m_filename; private: - using type_signal_remove = sigc::signal; + using type_signal_item_removed = sigc::signal; - type_signal_remove m_signal_remove; + type_signal_item_removed m_signal_item_removed; public: - type_signal_remove signal_remove(); + type_signal_item_removed signal_item_removed(); }; class ChatInputAttachmentContainer : public Gtk::ScrolledWindow { @@ -38,6 +43,7 @@ public: void Clear(); void ClearNoPurge(); bool AddImage(const Glib::RefPtr &pb); + bool AddFile(const Glib::RefPtr &file); [[nodiscard]] std::vector GetAttachments() const; private: @@ -92,6 +98,7 @@ public: void InsertText(const Glib::ustring &text); Glib::RefPtr GetBuffer(); bool ProcessKeyPress(GdkEventKey *event); + void AddAttachment(const Glib::RefPtr &file); private: Gtk::Revealer m_attachments_revealer; diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 3e1885b..d9d490b 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -170,6 +170,10 @@ void ChatWindow::SetTopic(const std::string &text) { m_topic.set_visible(text.length() > 0); } +void ChatWindow::AddAttachment(const Glib::RefPtr &file) { + m_input->AddAttachment(file); +} + #ifdef WITH_LIBHANDY void ChatWindow::OpenNewTab(Snowflake id) { // open if its the first tab (in which case it really isnt a tab but whatever) diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index ecbf666..917456b 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -35,6 +35,7 @@ public: Snowflake GetOldestListedMessage(); // oldest message that is currently in the ListBox void UpdateReactions(Snowflake id); void SetTopic(const std::string &text); + void AddAttachment(const Glib::RefPtr &file); #ifdef WITH_LIBHANDY void OpenNewTab(Snowflake id); diff --git a/src/discord/chatsubmitparams.hpp b/src/discord/chatsubmitparams.hpp index 0f0a0cd..6199634 100644 --- a/src/discord/chatsubmitparams.hpp +++ b/src/discord/chatsubmitparams.hpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "discord/snowflake.hpp" struct ChatSubmitParams { @@ -11,8 +12,9 @@ struct ChatSubmitParams { }; struct Attachment { - std::string Path; + Glib::RefPtr File; AttachmentType Type; + std::string Filename; }; Snowflake ChannelID; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 9849ae6..d26f1fe 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -480,13 +480,12 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, c 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"); + req.add_file(field_name, params.Attachments.at(i).File, params.Attachments.at(i).Filename); } 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); + attachment.File->remove(); } } ChatMessageCallback(nonce, res, callback); diff --git a/src/http.cpp b/src/http.cpp index beb1944..14fe3f5 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -35,7 +35,8 @@ request::request(request &&other) noexcept , m_method(std::exchange(other.m_method, nullptr)) , 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_form(std::exchange(other.m_form, nullptr)) + , m_read_streams(std::move(other.m_read_streams)) { // i think this is correct??? } @@ -82,12 +83,29 @@ void request::make_form() { m_form = curl_mime_init(m_curl); } +static size_t http_readfunc(char *buffer, size_t size, size_t nitems, void *arg) { + auto stream = Glib::wrap(G_FILE_INPUT_STREAM(arg), true); + int r = stream->read(buffer, size * nitems); + if (r == -1) { + // https://github.com/curl/curl/blob/ad9bc5976d6661cd5b03ebc379313bf657701c14/lib/mime.c#L724 + return size_t(-1); + } + return r; +} + // file must exist until request completes -void request::add_file(std::string_view name, std::string_view file_path, std::string_view filename) { +void request::add_file(std::string_view name, const Glib::RefPtr &file, std::string_view filename) { + if (!file->query_exists()) return; + auto *field = curl_mime_addpart(m_form); curl_mime_name(field, name.data()); - curl_mime_filedata(field, file_path.data()); + auto info = file->query_info(); + auto stream = file->read(); + curl_mime_data_cb(field, info->get_size(), http_readfunc, nullptr, nullptr, stream->gobj()); curl_mime_filename(field, filename.data()); + + // hold ref + m_read_streams.insert(stream); } // copied diff --git a/src/http.hpp b/src/http.hpp index 0c570db..a6f479d 100644 --- a/src/http.hpp +++ b/src/http.hpp @@ -1,9 +1,9 @@ #pragma once #include +#include #include #include - -// i regret not using snake case for everything oh well +#include namespace http { enum EStatusCode : int { @@ -115,7 +115,7 @@ struct request { void set_body(const std::string &data); void set_user_agent(const std::string &data); void make_form(); - void add_file(std::string_view name, std::string_view file_path, std::string_view filename); + void add_file(std::string_view name, const Glib::RefPtr &file, std::string_view filename); void add_field(std::string_view name, const char *data, size_t size); response execute(); @@ -129,6 +129,8 @@ private: curl_slist *m_header_list = nullptr; std::array m_error_buf = { 0 }; curl_mime *m_form = nullptr; + + std::set> m_read_streams; }; using response_type = response; diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index a6a17c5..17edfa3 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -76,6 +76,7 @@ MainWindow::MainWindow() add(m_main_box); SetupMenu(); + SetupDND(); } void MainWindow::UpdateComponents() { @@ -350,6 +351,22 @@ void MainWindow::SetupMenu() { #endif } +void MainWindow::SetupDND() { + std::vector targets; + targets.emplace_back("text/uri-list", Gtk::TargetFlags(0), 0); + drag_dest_set(targets, Gtk::DEST_DEFAULT_DROP | Gtk::DEST_DEFAULT_MOTION | Gtk::DEST_DEFAULT_HIGHLIGHT, Gdk::DragAction::ACTION_COPY); + signal_drag_data_received().connect([this](const Glib::RefPtr &ctx, int x, int y, const Gtk::SelectionData &selection, guint info, guint time) { + HandleDroppedURIs(selection); + }); +} + +void MainWindow::HandleDroppedURIs(const Gtk::SelectionData &selection) { + for (const auto &uri : selection.get_uris()) { + // not using Glib::get_filename_for_uri or whatever because the conversion is BAD (on windows at least) + m_chat.AddAttachment(Gio::File::create_for_uri(uri)); + } +} + MainWindow::type_signal_action_connect MainWindow::signal_action_connect() { return m_signal_action_connect; } diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index e67f6d2..b5b6fc1 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -39,6 +39,9 @@ public: private: void SetupMenu(); + void SetupDND(); + + void HandleDroppedURIs(const Gtk::SelectionData &selection); Gtk::Box m_main_box; Gtk::Box m_content_box; -- cgit v1.2.3 From d841a2c862eddc6b2053b48907f8e1400e2d1391 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 23 Jun 2022 00:48:00 -0400 Subject: add change filename --- src/abaddon.cpp | 10 ++++++++++ src/abaddon.hpp | 1 + src/components/chatinput.cpp | 15 +++++++++++++++ src/components/chatinput.hpp | 2 ++ src/dialogs/textinput.cpp | 26 ++++++++++++++++++++++++++ src/dialogs/textinput.hpp | 14 ++++++++++++++ 6 files changed, 68 insertions(+) create mode 100644 src/dialogs/textinput.cpp create mode 100644 src/dialogs/textinput.hpp diff --git a/src/abaddon.cpp b/src/abaddon.cpp index cfbf6b8..a34f444 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -11,6 +11,7 @@ #include "dialogs/setstatus.hpp" #include "dialogs/friendpicker.hpp" #include "dialogs/verificationgate.hpp" +#include "dialogs/textinput.hpp" #include "abaddon.hpp" #include "windows/guildsettingswindow.hpp" #include "windows/profilewindow.hpp" @@ -867,6 +868,15 @@ void Abaddon::ActionViewThreads(Snowflake channel_id) { window->show(); } +std::optional Abaddon::ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder, Gtk::Window *window) { + TextInputDialog dlg(prompt, title, placeholder, window != nullptr ? *window : *m_main_window); + const auto code = dlg.run(); + if (code == Gtk::RESPONSE_OK) + return dlg.GetInput(); + else + return {}; +} + bool Abaddon::ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window) { ConfirmDialog dlg(window != nullptr ? *window : *m_main_window); dlg.SetConfirmText(prompt); diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 1ba1251..c267269 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -51,6 +51,7 @@ public: void ActionViewPins(Snowflake channel_id); void ActionViewThreads(Snowflake channel_id); + std::optional ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder = "", Gtk::Window *window = nullptr); bool ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window = nullptr); void ActionReloadCSS(); diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 269a2ac..a734dec 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -220,6 +220,7 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & show_all_children(); SetupMenu(); + UpdateTooltip(); } Glib::RefPtr ChatInputAttachmentItem::GetFile() const { @@ -249,6 +250,16 @@ void ChatInputAttachmentItem::SetupMenu() { m_signal_item_removed.emit(); }); + m_menu_set_filename.set_label("Change Filename"); + m_menu_set_filename.signal_activate().connect([this] { + const auto name = Abaddon::Get().ShowTextPrompt("Enter new filename for attachment", "Enter filename", m_filename); + if (name.has_value()) { + m_filename = *name; + UpdateTooltip(); + } + }); + + m_menu.add(m_menu_set_filename); m_menu.add(m_menu_remove); m_menu.show_all(); @@ -262,6 +273,10 @@ void ChatInputAttachmentItem::SetupMenu() { }); } +void ChatInputAttachmentItem::UpdateTooltip() { + set_tooltip_text(m_filename); +} + ChatInputAttachmentItem::type_signal_item_removed ChatInputAttachmentItem::signal_item_removed() { return m_signal_item_removed; } diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index 865b23e..8a4ad8d 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -16,9 +16,11 @@ public: private: void SetupMenu(); + void UpdateTooltip(); Gtk::Menu m_menu; Gtk::MenuItem m_menu_remove; + Gtk::MenuItem m_menu_set_filename; Gtk::Box m_box; Gtk::Image *m_img = nullptr; diff --git a/src/dialogs/textinput.cpp b/src/dialogs/textinput.cpp new file mode 100644 index 0000000..ae75f70 --- /dev/null +++ b/src/dialogs/textinput.cpp @@ -0,0 +1,26 @@ +#include "textinput.hpp" + +TextInputDialog::TextInputDialog(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder, Gtk::Window &parent) + : Gtk::Dialog(title, parent, true) + , m_label(prompt) { + get_style_context()->add_class("app-window"); + get_style_context()->add_class("app-popup"); + + auto ok = add_button("OK", Gtk::RESPONSE_OK); + auto cancel = add_button("Cancel", Gtk::RESPONSE_CANCEL); + + get_content_area()->add(m_label); + get_content_area()->add(m_entry); + + m_entry.set_text(placeholder); + + m_entry.set_activates_default(true); + ok->set_can_default(true); + ok->grab_default(); + + show_all_children(); +} + +Glib::ustring TextInputDialog::GetInput() const { + return m_entry.get_text(); +} diff --git a/src/dialogs/textinput.hpp b/src/dialogs/textinput.hpp new file mode 100644 index 0000000..fd2d2b8 --- /dev/null +++ b/src/dialogs/textinput.hpp @@ -0,0 +1,14 @@ +#pragma once +#include +#include + +class TextInputDialog : public Gtk::Dialog { +public: + TextInputDialog(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder, Gtk::Window &parent); + + Glib::ustring GetInput() const; + +private: + Gtk::Label m_label; + Gtk::Entry m_entry; +}; -- cgit v1.2.3 From a038f47a255242cb452caac04b4b1a17a4bfd28e Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 23 Jun 2022 01:51:30 -0400 Subject: add icon to attachments without preview --- .github/workflows/ci.yml | 11 +++++++++++ ci/used-icons.txt | 1 + src/components/chatinput.cpp | 9 ++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 ci/used-icons.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5ec12e..bea5a2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,6 +73,17 @@ jobs: cp -r res/css res/res res/fonts build/artifactdir/bin cp /mingw64/share/glib-2.0/schemas/gschemas.compiled build/artifactdir/share/glib-2.0/schemas cat "ci/msys-deps.txt" | sed 's/\r$//' | xargs -I % cp /mingw64% build/artifactdir/bin || : + mkdir -p build/artifactdir/share/icons/Adwaita + cd build/artifactdir/share/icons/Adwaita + mkdir -p 16x16/actions 24x24/actions 32x32/actions 48x48/actions 64x64/actions 96x96/actions scalable/actions + cd ../../../../../ + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/16x16/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/16x16/actions || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/24x24/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/24x24/actions || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/32x32/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/32x32/actions || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/48x48/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/48x48/actions || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/64x64/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/64x64/actions || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/96x96/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/96x96/actions || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/scalable/actions/%.svg build/artifactdir/share/icons/Adwaita/scalable/actions || : - name: Upload build (1) uses: haya14busa/action-cond@v1 diff --git a/ci/used-icons.txt b/ci/used-icons.txt new file mode 100644 index 0000000..212a8d5 --- /dev/null +++ b/ci/used-icons.txt @@ -0,0 +1 @@ +document-send-symbolic diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index a734dec..cd508e8 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -188,15 +188,20 @@ ChatInputAttachmentContainer::type_signal_emptied ChatInputAttachmentContainer:: ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr &file) : m_file(file) - , m_img(Gtk::make_managed(Abaddon::Get().GetImageManager().GetPlaceholder(AttachmentItemSize))) + , m_img(Gtk::make_managed()) , m_type(ChatSubmitParams::ExtantFile) { get_style_context()->add_class("attachment-item"); set_size_request(AttachmentItemSize, AttachmentItemSize); + m_box.set_halign(Gtk::ALIGN_CENTER); + m_box.set_valign(Gtk::ALIGN_CENTER); m_box.add(*m_img); add(m_box); show_all_children(); + m_img->property_icon_name() = "document-send-symbolic"; + m_img->property_icon_size() = Gtk::ICON_SIZE_DIALOG; // todo figure out how to not use this weird property??? i dont know how icons work (screw your theme) + SetupMenu(); auto info = m_file->query_info(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); @@ -215,6 +220,8 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & m_img->property_pixbuf() = pb->scale_simple(outw, outh, Gdk::INTERP_BILINEAR); set_size_request(AttachmentItemSize, AttachmentItemSize); + m_box.set_halign(Gtk::ALIGN_CENTER); + m_box.set_valign(Gtk::ALIGN_CENTER); m_box.add(*m_img); add(m_box); show_all_children(); -- cgit v1.2.3 From e87766f106c1b8e4b57ce59c713fcf4efe6df39a Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 30 Jun 2022 04:20:42 -0400 Subject: dnd'd files show preview images if displayable --- src/components/chatinput.cpp | 63 ++++++++++++++++++++++++++++++++++++++------ src/components/chatinput.hpp | 7 +++-- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index cd508e8..203f960 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -67,6 +67,7 @@ void ChatInputText::on_grab_focus() { bool ChatInputText::CheckHandleClipboardPaste() { auto clip = Gtk::Clipboard::get(); + if (!clip->wait_is_image_available()) return false; const auto pb = clip->wait_for_image(); @@ -151,10 +152,14 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) return true; } -bool ChatInputAttachmentContainer::AddFile(const Glib::RefPtr &file) { +bool ChatInputAttachmentContainer::AddFile(const Glib::RefPtr &file, Glib::RefPtr pb) { if (m_attachments.size() == 10) return false; - auto *item = Gtk::make_managed(file); + ChatInputAttachmentItem *item; + if (pb) + item = Gtk::make_managed(file, pb, true); + else + item = Gtk::make_managed(file); item->show(); item->set_valign(Gtk::ALIGN_CENTER); m_box.add(*item); @@ -202,16 +207,16 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & m_img->property_icon_name() = "document-send-symbolic"; m_img->property_icon_size() = Gtk::ICON_SIZE_DIALOG; // todo figure out how to not use this weird property??? i dont know how icons work (screw your theme) - SetupMenu(); + SetFilenameFromFile(); - auto info = m_file->query_info(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); - m_filename = info->get_attribute_string(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); + SetupMenu(); + UpdateTooltip(); } -ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb) +ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb, bool is_extant) : m_file(file) , m_img(Gtk::make_managed()) - , m_type(ChatSubmitParams::PastedImage) + , m_type(is_extant ? ChatSubmitParams::ExtantFile : ChatSubmitParams::PastedImage) , m_filename("unknown.png") { get_style_context()->add_class("attachment-item"); @@ -226,6 +231,9 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & add(m_box); show_all_children(); + if (is_extant) + SetFilenameFromFile(); + SetupMenu(); UpdateTooltip(); } @@ -251,6 +259,11 @@ void ChatInputAttachmentItem::RemoveIfTemp() { m_file->remove(); } +void ChatInputAttachmentItem::SetFilenameFromFile() { + auto info = m_file->query_info(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); + m_filename = info->get_attribute_string(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); +} + void ChatInputAttachmentItem::SetupMenu() { m_menu_remove.set_label("Remove"); m_menu_remove.signal_activate().connect([this] { @@ -348,9 +361,43 @@ bool ChatInput::ProcessKeyPress(GdkEventKey *event) { void ChatInput::AddAttachment(const Glib::RefPtr &file) { const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); + if (!can_attach_files) return; + + std::string content_type; - if (can_attach_files && m_attachments.AddFile(file)) + try { + const auto info = file->query_info(G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE); + content_type = info->get_attribute_string(G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE); + } catch (const Gio::Error &err) { + printf("io error: %s\n", err.what()); + return; + } catch (...) { + puts("attachment query exception"); + return; + } + + static const std::unordered_set image_exts { + ".png", + ".jpg", + }; + + if (image_exts.find(content_type) != image_exts.end()) { + if (AddFileAsImageAttachment(file)) + m_attachments_revealer.set_reveal_child(true); + } else if (m_attachments.AddFile(file)) { m_attachments_revealer.set_reveal_child(true); + } +} + +bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr &file) { + try { + const auto read_stream = file->read(); + if (!read_stream) return false; + const auto pb = Gdk::Pixbuf::create_from_stream(read_stream); + return m_attachments.AddFile(file, pb); + } catch (...) { + return m_attachments.AddFile(file); + } } ChatInput::type_signal_submit ChatInput::signal_submit() { diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index 8a4ad8d..625161a 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -6,7 +6,7 @@ class ChatInputAttachmentItem : public Gtk::EventBox { public: ChatInputAttachmentItem(const Glib::RefPtr &file); - ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb); + ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb, bool is_extant = false); [[nodiscard]] Glib::RefPtr GetFile() const; [[nodiscard]] ChatSubmitParams::AttachmentType GetType() const; @@ -15,6 +15,7 @@ public: void RemoveIfTemp(); private: + void SetFilenameFromFile(); void SetupMenu(); void UpdateTooltip(); @@ -45,7 +46,7 @@ public: void Clear(); void ClearNoPurge(); bool AddImage(const Glib::RefPtr &pb); - bool AddFile(const Glib::RefPtr &file); + bool AddFile(const Glib::RefPtr &file, Glib::RefPtr pb = {}); [[nodiscard]] std::vector GetAttachments() const; private: @@ -103,6 +104,8 @@ public: void AddAttachment(const Glib::RefPtr &file); private: + bool AddFileAsImageAttachment(const Glib::RefPtr &file); + Gtk::Revealer m_attachments_revealer; ChatInputAttachmentContainer m_attachments; ChatInputText m_input; -- cgit v1.2.3 From a9d35dcccdd7ce6b57e0e9526a2eb1f5ca886013 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 3 Jul 2022 18:58:48 -0400 Subject: fix compile --- src/components/chatinput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 203f960..49c5b5d 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -369,7 +369,7 @@ void ChatInput::AddAttachment(const Glib::RefPtr &file) { const auto info = file->query_info(G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE); content_type = info->get_attribute_string(G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE); } catch (const Gio::Error &err) { - printf("io error: %s\n", err.what()); + printf("io error: %s\n", err.what().c_str()); return; } catch (...) { puts("attachment query exception"); -- cgit v1.2.3 From 41e2478a6f79d56234c674ebb5ab7f7c2954ee2d Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 4 Jul 2022 03:29:30 -0400 Subject: grab focus when adding attachment --- src/components/chatinput.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 49c5b5d..b71a234 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -382,10 +382,13 @@ void ChatInput::AddAttachment(const Glib::RefPtr &file) { }; if (image_exts.find(content_type) != image_exts.end()) { - if (AddFileAsImageAttachment(file)) + if (AddFileAsImageAttachment(file)) { m_attachments_revealer.set_reveal_child(true); + m_input.grab_focus(); + } } else if (m_attachments.AddFile(file)) { m_attachments_revealer.set_reveal_child(true); + m_input.grab_focus(); } } -- cgit v1.2.3 From 5c7631e71382b0c1727bd8e1487a7e41feaf2efc Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 5 Jul 2022 03:53:00 -0400 Subject: fix checks for is bot --- src/components/chatmessage.cpp | 4 ++-- src/discord/user.cpp | 4 ++++ src/discord/user.hpp | 1 + src/windows/guildsettings/memberspane.cpp | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 4d6eec5..863349f 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -1122,7 +1122,7 @@ ChatMessageHeader::ChatMessageHeader(const Message &data) m_meta_ev.signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageHeader::on_author_button_press)); - if (author->IsBot || data.WebhookID.has_value()) { + if (author->IsABot() || data.WebhookID.has_value()) { m_extra = Gtk::manage(new Gtk::Label); m_extra->get_style_context()->add_class("message-container-extra"); m_extra->set_single_line_mode(true); @@ -1130,7 +1130,7 @@ ChatMessageHeader::ChatMessageHeader(const Message &data) m_extra->set_can_focus(false); m_extra->set_use_markup(true); } - if (author->IsBot) + if (author->IsABot()) m_extra->set_markup("BOT"); else if (data.WebhookID.has_value()) m_extra->set_markup("Webhook"); diff --git a/src/discord/user.cpp b/src/discord/user.cpp index 4393992..0ab2af5 100644 --- a/src/discord/user.cpp +++ b/src/discord/user.cpp @@ -1,6 +1,10 @@ #include "user.hpp" #include "abaddon.hpp" +bool UserData::IsABot() const noexcept { + return IsBot.has_value() && *IsBot; +} + bool UserData::IsDeleted() const { return Discriminator == "0000"; } diff --git a/src/discord/user.hpp b/src/discord/user.hpp index 083f5c4..1b9d517 100644 --- a/src/discord/user.hpp +++ b/src/discord/user.hpp @@ -60,6 +60,7 @@ struct UserData { friend void to_json(nlohmann::json &j, const UserData &m); void update_from_json(const nlohmann::json &j); + [[nodiscard]] bool IsABot() const noexcept; [[nodiscard]] bool IsDeleted() const; [[nodiscard]] bool HasAvatar() const; [[nodiscard]] bool HasAnimatedAvatar() const noexcept; diff --git a/src/windows/guildsettings/memberspane.cpp b/src/windows/guildsettings/memberspane.cpp index 34650ad..973e380 100644 --- a/src/windows/guildsettings/memberspane.cpp +++ b/src/windows/guildsettings/memberspane.cpp @@ -204,7 +204,7 @@ void GuildSettingsMembersPaneInfo::SetUser(Snowflake user_id) { auto member = *discord.GetMember(user_id, GuildID); member.User = discord.GetUser(user_id); - m_bot.set_visible(member.User->IsBot.has_value() && *member.User->IsBot); + m_bot.set_visible(member.User->IsABot()); m_id.set_text("User ID: " + std::to_string(user_id)); m_created.set_text("Account created: " + user_id.GetLocalTimestamp()); -- 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 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 849ebf17f173b64bc981f8e641863b6c253f10d0 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 8 Jul 2022 02:27:09 -0400 Subject: try to properly clear chat list im sure this will break something somehow --- src/components/chatlist.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/chatlist.cpp b/src/components/chatlist.cpp index 9bd8daf..d2995ee 100644 --- a/src/components/chatlist.cpp +++ b/src/components/chatlist.cpp @@ -34,6 +34,9 @@ void ChatList::Clear() { delete *it; it++; } + m_id_to_widget.clear(); + m_num_messages = 0; + m_num_rows = 0; } void ChatList::SetActiveChannel(Snowflake id) { -- cgit v1.2.3 From 241d9a2140b07acb4212c1fc79b70f1f36be9c78 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 8 Jul 2022 02:27:21 -0400 Subject: fix compile --- src/discord/discord.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index e155728..1c389f7 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -479,7 +479,7 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, c 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), + sigc::mem_fun(m_signal_message_progress, type_signal_message_progress::emit), nonce, static_cast(ulnow) / static_cast(ultotal))); m_generic_dispatch.emit(); -- cgit v1.2.3 From 02ce353c6d35af004dc1b6f5ae9f68fbb8540b54 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 9 Jul 2022 01:57:56 -0400 Subject: check nitro size restriction + fix replying border --- res/css/main.css | 5 +++++ src/components/chatinput.cpp | 17 +++++++++++++++++ src/components/chatinput.hpp | 4 ++++ src/components/chatwindow.cpp | 24 +++++++++++++++++++++--- src/constants.hpp | 5 +++++ 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/res/css/main.css b/res/css/main.css index a3d0421..142dede 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -102,12 +102,17 @@ background-color: #242424; color: #adadad; border-radius: 15px; + border: 1px solid transparent; } .message-input.replying { border: 1px solid #026FB9; } +.message-input.bad-input { + border: 1px solid #dd3300; +} + .message-input { padding: 0px 0px 0px 5px; } diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index b71a234..fc5ef38 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -392,6 +392,23 @@ void ChatInput::AddAttachment(const Glib::RefPtr &file) { } } +void ChatInput::IndicateTooLarge() { + m_input.get_style_context()->add_class("bad-input"); + const auto cb = [this] { + m_input.get_style_context()->remove_class("bad-input"); + }; + Glib::signal_timeout().connect_seconds_once(sigc::track_obj(cb, *this), 2); +} + +void ChatInput::StartReplying() { + m_input.grab_focus(); + m_input.get_style_context()->add_class("replying"); +} + +void ChatInput::StopReplying() { + m_input.get_style_context()->remove_class("replying"); +} + 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 625161a..2b88fd1 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -102,6 +102,10 @@ public: Glib::RefPtr GetBuffer(); bool ProcessKeyPress(GdkEventKey *event); void AddAttachment(const Glib::RefPtr &file); + void IndicateTooLarge(); + + void StartReplying(); + void StopReplying(); private: bool AddFileAsImageAttachment(const Glib::RefPtr &file); diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 5cbeea1..61d3712 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -4,6 +4,7 @@ #include "ratelimitindicator.hpp" #include "chatinput.hpp" #include "chatlist.hpp" +#include "constants.hpp" #ifdef WITH_LIBHANDY #include "channeltabswitcherhandy.hpp" #endif @@ -222,6 +223,24 @@ Snowflake ChatWindow::GetActiveChannel() const { } bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { + int restriction = BaseAttachmentSizeLimit; + const auto nitro = Abaddon::Get().GetDiscordClient().GetUserData().PremiumType; + if (!nitro.has_value() || nitro == EPremiumType::None) { + restriction = BaseAttachmentSizeLimit; + } else if (nitro == EPremiumType::NitroClassic) { + restriction = NitroClassicAttachmentSizeLimit; + } else if (nitro == EPremiumType::Nitro) { + restriction = NitroAttachmentSizeLimit; + } + + for (const auto &attachment : data.Attachments) { + const auto info = attachment.File->query_info(); + if (info && info->get_size() > restriction) { + m_input->IndicateTooLarge(); + return false; + } + } + if (!m_rate_limit_indicator->CanSpeak()) return false; @@ -255,8 +274,7 @@ void ChatWindow::StartReplying(Snowflake message_id) { const auto author = discord.GetUser(message.Author.ID); m_replying_to = message_id; m_is_replying = true; - m_input->grab_focus(); - m_input->get_style_context()->add_class("replying"); + m_input->StartReplying(); if (author.has_value()) m_input_indicator->SetCustomMarkup("Replying to " + author->GetEscapedBoldString()); else @@ -266,7 +284,7 @@ void ChatWindow::StartReplying(Snowflake message_id) { void ChatWindow::StopReplying() { m_is_replying = false; m_replying_to = Snowflake::Invalid; - m_input->get_style_context()->remove_class("replying"); + m_input->StopReplying(); m_input_indicator->ClearCustom(); } diff --git a/src/constants.hpp b/src/constants.hpp index 30de2ae..9b2413d 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -3,3 +3,8 @@ 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 = 128; +constexpr static int BaseAttachmentSizeLimit = 8 * 1024 * 1024; +constexpr static int NitroClassicAttachmentSizeLimit = 50 * 1024 * 1024; +constexpr static int NitroAttachmentSizeLimit = 100 * 1024 * 1024; +constexpr static int BoostLevel2AttachmentSizeLimit = 50 * 1024 * 1024; +constexpr static int BoostLevel3AttachmentSizeLimit = 100 * 1024 * 1024; -- cgit v1.2.3 From f95d79129e16f01dee6b58f76f55726aed7a0d41 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 9 Jul 2022 03:05:48 -0400 Subject: handle premium server upload size limits --- src/components/chatwindow.cpp | 27 ++++++++++++++++++++++----- src/discord/guild.hpp | 9 ++++++++- src/discord/store.cpp | 1 + 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 61d3712..38e0247 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -223,16 +223,33 @@ Snowflake ChatWindow::GetActiveChannel() const { } bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { - int restriction = BaseAttachmentSizeLimit; - const auto nitro = Abaddon::Get().GetDiscordClient().GetUserData().PremiumType; + auto &discord = Abaddon::Get().GetDiscordClient(); + + int nitro_restriction = BaseAttachmentSizeLimit; + const auto nitro = discord.GetUserData().PremiumType; if (!nitro.has_value() || nitro == EPremiumType::None) { - restriction = BaseAttachmentSizeLimit; + nitro_restriction = BaseAttachmentSizeLimit; } else if (nitro == EPremiumType::NitroClassic) { - restriction = NitroClassicAttachmentSizeLimit; + nitro_restriction = NitroClassicAttachmentSizeLimit; } else if (nitro == EPremiumType::Nitro) { - restriction = NitroAttachmentSizeLimit; + nitro_restriction = NitroAttachmentSizeLimit; + } + + int guild_restriction = BaseAttachmentSizeLimit; + if (const auto channel = discord.GetChannel(m_active_channel); channel.has_value() && channel->GuildID.has_value()) { + if (const auto guild = discord.GetGuild(*channel->GuildID); guild.has_value()) { + if (!guild->PremiumTier.has_value() || guild->PremiumTier == GuildPremiumTier::NONE || guild->PremiumTier == GuildPremiumTier::TIER_1) { + guild_restriction = BaseAttachmentSizeLimit; + } else if (guild->PremiumTier == GuildPremiumTier::TIER_2) { + guild_restriction = BoostLevel2AttachmentSizeLimit; + } else if (guild->PremiumTier == GuildPremiumTier::TIER_3) { + guild_restriction = BoostLevel3AttachmentSizeLimit; + } + } } + int restriction = std::max(nitro_restriction, guild_restriction); + for (const auto &attachment : data.Attachments) { const auto info = attachment.File->query_info(); if (info && info->get_size() > restriction) { diff --git a/src/discord/guild.hpp b/src/discord/guild.hpp index 152e250..0428928 100644 --- a/src/discord/guild.hpp +++ b/src/discord/guild.hpp @@ -16,6 +16,13 @@ enum class GuildApplicationStatus { UNKNOWN, }; +enum class GuildPremiumTier { + NONE = 0, + TIER_1 = 1, + TIER_2 = 2, + TIER_3 = 3, +}; + struct GuildApplicationData { Snowflake UserID; Snowflake GuildID; @@ -73,7 +80,7 @@ struct GuildData { std::optional VanityURL; // null std::optional Description; // null std::optional BannerHash; // null - std::optional PremiumTier; + std::optional PremiumTier; std::optional PremiumSubscriptionCount; std::optional PreferredLocale; std::optional PublicUpdatesChannelID; // null diff --git a/src/discord/store.cpp b/src/discord/store.cpp index 998bc41..663d113 100644 --- a/src/discord/store.cpp +++ b/src/discord/store.cpp @@ -738,6 +738,7 @@ std::optional Store::GetGuild(Snowflake id) const { s->Get(2, r.Icon); s->Get(5, r.OwnerID); s->Get(20, r.IsUnavailable); + s->Get(27, r.PremiumTier); s->Reset(); -- cgit v1.2.3 From fba5cee519f74490a0fc3064d29c2a27a0ea4ed1 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 10 Jul 2022 02:38:19 +0000 Subject: try to fix compile again --- src/discord/discord.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 1c389f7..34688ba 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -478,10 +478,11 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, c 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, type_signal_message_progress::emit), - nonce, - static_cast(ulnow) / static_cast(ultotal))); + m_generic_queue.push([this, nonce, ultotal, ulnow] { + m_signal_message_progress.emit( + nonce, + static_cast(ulnow) / static_cast(ultotal)); + }); m_generic_dispatch.emit(); m_generic_mutex.unlock(); }); -- cgit v1.2.3 From 111399cf4a161b8bca2ab938e79b943af4aa4d3d Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 14 Jul 2022 03:13:27 -0400 Subject: move progress bar so it doesnt conflict with other stuff --- res/css/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/main.css b/res/css/main.css index 142dede..968296d 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -331,6 +331,7 @@ .message-progress { border: none; + margin-bottom: -8px; } .message-progress trough { -- cgit v1.2.3 From 544ae6f915da175c489e4293d1d5b51e5fdde4f7 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 14 Jul 2022 04:12:17 -0400 Subject: fix potential deadlock --- src/discord/discord.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 34688ba..21c0b87 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -483,8 +483,8 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, c nonce, static_cast(ulnow) / static_cast(ultotal)); }); - m_generic_dispatch.emit(); m_generic_mutex.unlock(); + m_generic_dispatch.emit(); }); req.make_form(); req.add_field("payload_json", nlohmann::json(obj).dump().c_str(), CURL_ZERO_TERMINATED); -- cgit v1.2.3 From 59acd0f82f500d743ca4be7324f913903c8de237 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 23 Jul 2022 18:34:33 -0400 Subject: handle max message payload + show filename label --- src/components/chatinput.cpp | 32 +++++++++++++++++++++++++++----- src/components/chatinput.hpp | 1 + src/components/chatwindow.cpp | 15 ++++++++++++--- src/constants.hpp | 1 + 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index fc5ef38..04ba0d9 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -2,7 +2,6 @@ #include "abaddon.hpp" #include "constants.hpp" #include -#include ChatInputText::ChatInputText() { get_style_context()->add_class("message-input"); @@ -96,6 +95,8 @@ ChatInputAttachmentContainer::ChatInputAttachmentContainer() : m_box(Gtk::ORIENTATION_HORIZONTAL) { get_style_context()->add_class("attachment-container"); + m_box.set_halign(Gtk::ALIGN_START); + add(m_box); m_box.show(); @@ -194,16 +195,25 @@ ChatInputAttachmentContainer::type_signal_emptied ChatInputAttachmentContainer:: ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr &file) : m_file(file) , m_img(Gtk::make_managed()) - , m_type(ChatSubmitParams::ExtantFile) { + , m_type(ChatSubmitParams::ExtantFile) + , m_box(Gtk::ORIENTATION_VERTICAL) { get_style_context()->add_class("attachment-item"); set_size_request(AttachmentItemSize, AttachmentItemSize); - m_box.set_halign(Gtk::ALIGN_CENTER); + set_halign(Gtk::ALIGN_START); + m_box.set_hexpand(true); + m_box.set_halign(Gtk::ALIGN_FILL); m_box.set_valign(Gtk::ALIGN_CENTER); m_box.add(*m_img); + m_box.add(m_label); add(m_box); show_all_children(); + m_label.set_max_width_chars(0); // will constrain to given size + m_label.set_ellipsize(Pango::ELLIPSIZE_MIDDLE); + m_label.set_margin_start(7); + m_label.set_margin_end(7); + m_img->property_icon_name() = "document-send-symbolic"; m_img->property_icon_size() = Gtk::ICON_SIZE_DIALOG; // todo figure out how to not use this weird property??? i dont know how icons work (screw your theme) @@ -217,7 +227,9 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & : m_file(file) , m_img(Gtk::make_managed()) , m_type(is_extant ? ChatSubmitParams::ExtantFile : ChatSubmitParams::PastedImage) - , m_filename("unknown.png") { + , m_filename("unknown.png") + , m_label("unknown.png") + , m_box(Gtk::ORIENTATION_VERTICAL) { get_style_context()->add_class("attachment-item"); int outw, outh; @@ -225,12 +237,20 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & m_img->property_pixbuf() = pb->scale_simple(outw, outh, Gdk::INTERP_BILINEAR); set_size_request(AttachmentItemSize, AttachmentItemSize); - m_box.set_halign(Gtk::ALIGN_CENTER); + set_halign(Gtk::ALIGN_START); + m_box.set_hexpand(true); + m_box.set_halign(Gtk::ALIGN_FILL); m_box.set_valign(Gtk::ALIGN_CENTER); m_box.add(*m_img); + m_box.add(m_label); add(m_box); show_all_children(); + m_label.set_max_width_chars(0); // will constrain to given size + m_label.set_ellipsize(Pango::ELLIPSIZE_MIDDLE); + m_label.set_margin_start(7); + m_label.set_margin_end(7); + if (is_extant) SetFilenameFromFile(); @@ -262,6 +282,7 @@ void ChatInputAttachmentItem::RemoveIfTemp() { void ChatInputAttachmentItem::SetFilenameFromFile() { auto info = m_file->query_info(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); m_filename = info->get_attribute_string(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); + m_label.set_text(m_filename); } void ChatInputAttachmentItem::SetupMenu() { @@ -275,6 +296,7 @@ void ChatInputAttachmentItem::SetupMenu() { const auto name = Abaddon::Get().ShowTextPrompt("Enter new filename for attachment", "Enter filename", m_filename); if (name.has_value()) { m_filename = *name; + m_label.set_text(m_filename); UpdateTooltip(); } }); diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index 2b88fd1..d8484b8 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -24,6 +24,7 @@ private: Gtk::MenuItem m_menu_set_filename; Gtk::Box m_box; + Gtk::Label m_label; Gtk::Image *m_img = nullptr; Glib::RefPtr m_file; diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 38e0247..b68ceba 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -250,11 +250,20 @@ bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { int restriction = std::max(nitro_restriction, guild_restriction); + goffset total_size = 0; for (const auto &attachment : data.Attachments) { const auto info = attachment.File->query_info(); - if (info && info->get_size() > restriction) { - m_input->IndicateTooLarge(); - return false; + if (info) { + const auto size = info->get_size(); + if (size > restriction) { + m_input->IndicateTooLarge(); + return false; + } + total_size += size; + if (total_size > MaxMessagePayloadSize) { + m_input->IndicateTooLarge(); + return false; + } } } diff --git a/src/constants.hpp b/src/constants.hpp index 9b2413d..14fa076 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -8,3 +8,4 @@ constexpr static int NitroClassicAttachmentSizeLimit = 50 * 1024 * 1024; constexpr static int NitroAttachmentSizeLimit = 100 * 1024 * 1024; constexpr static int BoostLevel2AttachmentSizeLimit = 50 * 1024 * 1024; constexpr static int BoostLevel3AttachmentSizeLimit = 100 * 1024 * 1024; +constexpr static int MaxMessagePayloadSize = 199 * 1024 * 1024; -- cgit v1.2.3 From 8396d07d9e7c8a3b7d7942694bd82c433eda99b4 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 25 Jul 2022 00:26:04 -0400 Subject: try to align stuff a little better --- src/components/chatinput.cpp | 19 +++++++++++++++---- src/constants.hpp | 3 ++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 04ba0d9..77757cf 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -135,8 +135,10 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) } auto *item = Gtk::make_managed(Gio::File::create_for_path(path), pb); + item->set_valign(Gtk::ALIGN_FILL); + item->set_vexpand(true); + item->set_margin_bottom(5); item->show(); - item->set_valign(Gtk::ALIGN_CENTER); m_box.add(*item); m_attachments.push_back(item); @@ -161,8 +163,10 @@ bool ChatInputAttachmentContainer::AddFile(const Glib::RefPtr &file, item = Gtk::make_managed(file, pb, true); else item = Gtk::make_managed(file); + item->set_valign(Gtk::ALIGN_FILL); + item->set_vexpand(true); + item->set_margin_bottom(5); item->show(); - item->set_valign(Gtk::ALIGN_CENTER); m_box.add(*item); m_attachments.push_back(item); @@ -202,18 +206,21 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & set_size_request(AttachmentItemSize, AttachmentItemSize); set_halign(Gtk::ALIGN_START); m_box.set_hexpand(true); + m_box.set_vexpand(true); m_box.set_halign(Gtk::ALIGN_FILL); - m_box.set_valign(Gtk::ALIGN_CENTER); + m_box.set_valign(Gtk::ALIGN_FILL); m_box.add(*m_img); m_box.add(m_label); add(m_box); show_all_children(); + m_label.set_valign(Gtk::ALIGN_END); m_label.set_max_width_chars(0); // will constrain to given size m_label.set_ellipsize(Pango::ELLIPSIZE_MIDDLE); m_label.set_margin_start(7); m_label.set_margin_end(7); + m_img->set_vexpand(true); m_img->property_icon_name() = "document-send-symbolic"; m_img->property_icon_size() = Gtk::ICON_SIZE_DIALOG; // todo figure out how to not use this weird property??? i dont know how icons work (screw your theme) @@ -239,18 +246,22 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & set_size_request(AttachmentItemSize, AttachmentItemSize); set_halign(Gtk::ALIGN_START); m_box.set_hexpand(true); + m_box.set_vexpand(true); m_box.set_halign(Gtk::ALIGN_FILL); - m_box.set_valign(Gtk::ALIGN_CENTER); + m_box.set_valign(Gtk::ALIGN_FILL); m_box.add(*m_img); m_box.add(m_label); add(m_box); show_all_children(); + m_label.set_valign(Gtk::ALIGN_END); m_label.set_max_width_chars(0); // will constrain to given size m_label.set_ellipsize(Pango::ELLIPSIZE_MIDDLE); m_label.set_margin_start(7); m_label.set_margin_end(7); + m_img->set_vexpand(true); + if (is_extant) SetFilenameFromFile(); diff --git a/src/constants.hpp b/src/constants.hpp index 14fa076..b1ef82b 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -1,8 +1,9 @@ +#pragma once #include 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 = 128; +constexpr static int AttachmentItemSize = 120; constexpr static int BaseAttachmentSizeLimit = 8 * 1024 * 1024; constexpr static int NitroClassicAttachmentSizeLimit = 50 * 1024 * 1024; constexpr static int NitroAttachmentSizeLimit = 100 * 1024 * 1024; -- 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(+) 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 From e8260c164fd7e49ee9f43da334f332b5a9ec2a0b Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 31 Jul 2022 17:53:55 -0400 Subject: stop printing every event type --- src/discord/discord.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 5f49805..d2c8edd 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1335,7 +1335,6 @@ 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()); -- cgit v1.2.3 From 3487353fc79abb32481282be9d3ba4d54730b1e2 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 7 Aug 2022 02:14:26 -0400 Subject: css tweaks --- res/css/main.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/res/css/main.css b/res/css/main.css index 968296d..6d693bd 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -101,10 +101,15 @@ .message-input, .message-input textview, .message-input textview text { background-color: #242424; color: #adadad; - border-radius: 15px; + border-radius: 3px; border: 1px solid transparent; } +.message-input { + border: 1px solid #444444; + margin-right: 15px; +} + .message-input.replying { border: 1px solid #026FB9; } -- cgit v1.2.3 From 344f2694141aa0cc8fc214dfd8ce2011df1a8079 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 7 Aug 2022 02:16:20 -0400 Subject: add file picker to chat input --- res/css/main.css | 6 ++- src/components/chatinput.cpp | 104 ++++++++++++++++++++++++++++++++++++++++--- src/components/chatinput.hpp | 26 ++++++++++- 3 files changed, 128 insertions(+), 8 deletions(-) diff --git a/res/css/main.css b/res/css/main.css index 6d693bd..3765498 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -118,8 +118,12 @@ border: 1px solid #dd3300; } +.message-input-browse-icon { + color: #b9bbbe; +} + .message-input { - padding: 0px 0px 0px 5px; + padding: 0px 0px 0px 30px; } .members { diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 13e2550..7ecd4de 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -91,6 +91,95 @@ ChatInputText::type_signal_image_paste ChatInputText::signal_image_paste() { return m_signal_image_paste; } +ChatInputTextContainer::ChatInputTextContainer() { + // triple hack !!! + auto cb = [this](GdkEventKey *e) -> bool { + return event(reinterpret_cast(e)); + }; + m_input.signal_key_press_event().connect(cb, false); + + m_upload_img.property_icon_name() = "document-send-symbolic"; + m_upload_img.property_icon_size() = Gtk::ICON_SIZE_LARGE_TOOLBAR; + m_upload_img.get_style_context()->add_class("message-input-browse-icon"); + + AddPointerCursor(m_upload_ev); + + m_upload_ev.signal_button_press_event().connect([this](GdkEventButton *ev) -> bool { + if (ev->button == GDK_BUTTON_PRIMARY) { + ShowFileChooser(); + return true; + } + return false; + }); + + m_upload_ev.add(m_upload_img); + add_overlay(m_upload_ev); + add(m_input); + + show_all_children(); + + // stop the overlay from using (start) padding + signal_get_child_position().connect(sigc::mem_fun(*this, &ChatInputTextContainer::GetChildPosition), false); +} + +void ChatInputTextContainer::ShowFileChooser() { + auto dlg = Gtk::FileChooserNative::create("Choose file", Gtk::FILE_CHOOSER_ACTION_OPEN); + dlg->set_select_multiple(true); + dlg->set_modal(true); + + dlg->signal_response().connect([this, dlg](int response) { + if (response == Gtk::RESPONSE_ACCEPT) { + for (const auto &file : dlg->get_files()) { + m_signal_add_attachment.emit(file); + } + } + }); + + auto filter_all = Gtk::FileFilter::create(); + filter_all->set_name("All files (*.*)"); + filter_all->add_pattern("*.*"); + dlg->add_filter(filter_all); + + dlg->run(); +} + +ChatInputText &ChatInputTextContainer::Get() { + return m_input; +} + +bool ChatInputTextContainer::GetChildPosition(Gtk::Widget *child, Gdk::Rectangle &pos) { + Gtk::Allocation main_alloc; + { + auto *grandchild = m_input.get_child(); + int x, y; + if (grandchild->translate_coordinates(m_input, 0, 0, x, y)) { + main_alloc.set_x(x); + main_alloc.set_y(y); + } else { + main_alloc.set_x(0); + main_alloc.set_y(0); + } + main_alloc.set_width(grandchild->get_allocated_width()); + main_alloc.set_height(grandchild->get_allocated_height()); + } + + Gtk::Requisition min, req; + child->get_preferred_size(min, req); + + // yummy hardcoded values + pos.set_x(5); + pos.set_width(std::max(min.width, std::min(main_alloc.get_width(), req.width))); + + pos.set_y(12); + pos.set_height(std::max(min.height, std::min(main_alloc.get_height(), req.height))); + + return true; +} + +ChatInputTextContainer::type_signal_add_attachment ChatInputTextContainer::signal_add_attachment() { + return m_signal_add_attachment; +} + ChatInputAttachmentContainer::ChatInputAttachmentContainer() : m_box(Gtk::ORIENTATION_HORIZONTAL) { get_style_context()->add_class("attachment-container"); @@ -336,12 +425,15 @@ ChatInputAttachmentItem::type_signal_item_removed ChatInputAttachmentItem::signa ChatInput::ChatInput() : Gtk::Box(Gtk::ORIENTATION_VERTICAL) { - m_input.signal_escape().connect([this] { + m_input.signal_add_attachment().connect(sigc::mem_fun(*this, &ChatInput::AddAttachment)); + + m_input.Get().signal_escape().connect([this] { m_attachments.Clear(); m_attachments_revealer.set_reveal_child(false); m_signal_escape.emit(); }); - m_input.signal_submit().connect([this](const Glib::ustring &input) -> bool { + + m_input.Get().signal_submit().connect([this](const Glib::ustring &input) -> bool { ChatSubmitParams data; data.Message = input; data.Attachments = m_attachments.GetAttachments(); @@ -362,7 +454,7 @@ ChatInput::ChatInput() add(m_input); show_all_children(); - m_input.signal_image_paste().connect([this](const Glib::RefPtr &pb) { + m_input.Get().signal_image_paste().connect([this](const Glib::RefPtr &pb) { const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); if (can_attach_files && m_attachments.AddImage(pb)) @@ -381,15 +473,15 @@ ChatInput::ChatInput() } void ChatInput::InsertText(const Glib::ustring &text) { - m_input.InsertText(text); + m_input.Get().InsertText(text); } Glib::RefPtr ChatInput::GetBuffer() { - return m_input.GetBuffer(); + return m_input.Get().GetBuffer(); } bool ChatInput::ProcessKeyPress(GdkEventKey *event) { - return m_input.ProcessKeyPress(event); + return m_input.Get().ProcessKeyPress(event); } void ChatInput::AddAttachment(const Glib::RefPtr &file) { diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index d8484b8..82ea2fd 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -95,6 +95,30 @@ private: type_signal_image_paste m_signal_image_paste; }; +// file upload, text +class ChatInputTextContainer : public Gtk::Overlay { +public: + ChatInputTextContainer(); + + // not proxying everythign lol!! + ChatInputText &Get(); + +private: + void ShowFileChooser(); + bool GetChildPosition(Gtk::Widget *child, Gdk::Rectangle &pos); + + Gtk::EventBox m_upload_ev; + Gtk::Image m_upload_img; + ChatInputText m_input; + +public: + using type_signal_add_attachment = sigc::signal>; + type_signal_add_attachment signal_add_attachment(); + +private: + type_signal_add_attachment m_signal_add_attachment; +}; + class ChatInput : public Gtk::Box { public: ChatInput(); @@ -113,7 +137,7 @@ private: Gtk::Revealer m_attachments_revealer; ChatInputAttachmentContainer m_attachments; - ChatInputText m_input; + ChatInputTextContainer m_input; public: using type_signal_submit = sigc::signal; -- cgit v1.2.3 From c0e4a3a98805d61ca0556cba6e1b94c62d3ab4cc Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 7 Aug 2022 19:18:05 -0400 Subject: give focus to input after choosing file --- src/components/chatinput.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 7ecd4de..fd594d2 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -107,6 +107,8 @@ ChatInputTextContainer::ChatInputTextContainer() { m_upload_ev.signal_button_press_event().connect([this](GdkEventButton *ev) -> bool { if (ev->button == GDK_BUTTON_PRIMARY) { ShowFileChooser(); + // return focus + m_input.grab_focus(); return true; } return false; -- cgit v1.2.3 From 537d4163c28140adf28182523b9120fa75399d99 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 7 Aug 2022 19:19:52 -0400 Subject: add some Get()s that i forgot somehow --- src/components/chatinput.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index fd594d2..65b3c30 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -511,29 +511,29 @@ void ChatInput::AddAttachment(const Glib::RefPtr &file) { if (image_exts.find(content_type) != image_exts.end()) { if (AddFileAsImageAttachment(file)) { m_attachments_revealer.set_reveal_child(true); - m_input.grab_focus(); + m_input.Get().grab_focus(); } } else if (m_attachments.AddFile(file)) { m_attachments_revealer.set_reveal_child(true); - m_input.grab_focus(); + m_input.Get().grab_focus(); } } void ChatInput::IndicateTooLarge() { - m_input.get_style_context()->add_class("bad-input"); + m_input.Get().get_style_context()->add_class("bad-input"); const auto cb = [this] { - m_input.get_style_context()->remove_class("bad-input"); + m_input.Get().get_style_context()->remove_class("bad-input"); }; Glib::signal_timeout().connect_seconds_once(sigc::track_obj(cb, *this), 2); } void ChatInput::StartReplying() { - m_input.grab_focus(); - m_input.get_style_context()->add_class("replying"); + m_input.Get().grab_focus(); + m_input.Get().get_style_context()->add_class("replying"); } void ChatInput::StopReplying() { - m_input.get_style_context()->remove_class("replying"); + m_input.Get().get_style_context()->remove_class("replying"); } bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr &file) { -- cgit v1.2.3 From 91527fbd0d11f639e58b9ea6f77aaa1b48e1f593 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 7 Aug 2022 19:54:39 -0400 Subject: pull chat input permission check out of signal --- src/components/chatinput.cpp | 19 ++++++++++--------- src/components/chatinput.hpp | 8 +++++--- src/components/chatwindow.cpp | 4 +--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 65b3c30..63bd48b 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -457,9 +457,7 @@ ChatInput::ChatInput() show_all_children(); m_input.Get().signal_image_paste().connect([this](const Glib::RefPtr &pb) { - const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); - - if (can_attach_files && m_attachments.AddImage(pb)) + if (CanAttachFiles() && m_attachments.AddImage(pb)) m_attachments_revealer.set_reveal_child(true); }); @@ -487,8 +485,7 @@ bool ChatInput::ProcessKeyPress(GdkEventKey *event) { } void ChatInput::AddAttachment(const Glib::RefPtr &file) { - const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); - if (!can_attach_files) return; + if (!CanAttachFiles()) return; std::string content_type; @@ -527,6 +524,10 @@ void ChatInput::IndicateTooLarge() { Glib::signal_timeout().connect_seconds_once(sigc::track_obj(cb, *this), 2); } +void ChatInput::SetActiveChannel(Snowflake id) { + m_active_channel = id; +} + void ChatInput::StartReplying() { m_input.Get().grab_focus(); m_input.Get().get_style_context()->add_class("replying"); @@ -547,6 +548,10 @@ bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr &file) { } } +bool ChatInput::CanAttachFiles() { + return Abaddon::Get().GetDiscordClient().HasSelfChannelPermission(m_active_channel, Permission::ATTACH_FILES); +} + ChatInput::type_signal_submit ChatInput::signal_submit() { return m_signal_submit; } @@ -554,7 +559,3 @@ 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 82ea2fd..f6c4358 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -129,27 +129,29 @@ public: void AddAttachment(const Glib::RefPtr &file); void IndicateTooLarge(); + void SetActiveChannel(Snowflake id); + void StartReplying(); void StopReplying(); private: bool AddFileAsImageAttachment(const Glib::RefPtr &file); + bool CanAttachFiles(); Gtk::Revealer m_attachments_revealer; ChatInputAttachmentContainer m_attachments; ChatInputTextContainer m_input; + Snowflake m_active_channel; + public: 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 b68ceba..f7c3459 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -49,9 +49,6 @@ 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) @@ -133,6 +130,7 @@ void ChatWindow::SetMessages(const std::vector &msgs) { void ChatWindow::SetActiveChannel(Snowflake id) { m_active_channel = id; m_chat->SetActiveChannel(id); + m_input->SetActiveChannel(id); m_input_indicator->SetActiveChannel(id); m_rate_limit_indicator->SetActiveChannel(id); if (m_is_replying) -- cgit v1.2.3 From 1c3867135619d30104229fa26e2daa70f0999373 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 7 Aug 2022 19:56:01 -0400 Subject: add SEND_MESSAGES permission check finally --- src/components/chatwindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index f7c3459..58e36d0 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -222,6 +222,7 @@ Snowflake ChatWindow::GetActiveChannel() const { bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { auto &discord = Abaddon::Get().GetDiscordClient(); + if (!discord.HasSelfChannelPermission(m_active_channel, Permission::SEND_MESSAGES)) return false; int nitro_restriction = BaseAttachmentSizeLimit; const auto nitro = discord.GetUserData().PremiumType; -- cgit v1.2.3 From 53ac8533671d08e96896b80cd513ba05aabd930f Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 8 Aug 2022 00:29:18 -0400 Subject: fix some permission checks --- src/components/chatinput.cpp | 2 +- src/components/chatwindow.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 63bd48b..75dda8d 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -549,7 +549,7 @@ bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr &file) { } bool ChatInput::CanAttachFiles() { - return Abaddon::Get().GetDiscordClient().HasSelfChannelPermission(m_active_channel, Permission::ATTACH_FILES); + return 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/chatwindow.cpp b/src/components/chatwindow.cpp index 58e36d0..c5b4d14 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -223,6 +223,7 @@ Snowflake ChatWindow::GetActiveChannel() const { bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { auto &discord = Abaddon::Get().GetDiscordClient(); if (!discord.HasSelfChannelPermission(m_active_channel, Permission::SEND_MESSAGES)) return false; + if (!data.Attachments.empty() && !discord.HasSelfChannelPermission(m_active_channel, Permission::ATTACH_FILES)) return false; int nitro_restriction = BaseAttachmentSizeLimit; const auto nitro = discord.GetUserData().PremiumType; -- cgit v1.2.3 From 955b9239b9eac748d3e5ac4dc56864c906c81393 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 8 Aug 2022 00:40:20 -0400 Subject: hide browse icon when not in channel with perms --- res/css/main.css | 7 ++++++- src/components/chatinput.cpp | 17 +++++++++++++++++ src/components/chatinput.hpp | 3 +++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/res/css/main.css b/res/css/main.css index 3765498..9398d48 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -122,7 +122,12 @@ color: #b9bbbe; } -.message-input { +/* i dont think theres a way to circumvent having to do this to adjust around the browse icon */ +.message-input:not(.with-browser-icon) { + padding: 0px 0px 0px 5px; +} + +.message-input.with-browse-icon { padding: 0px 0px 0px 30px; } diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 75dda8d..87892e0 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -149,6 +149,14 @@ ChatInputText &ChatInputTextContainer::Get() { return m_input; } +void ChatInputTextContainer::ShowChooserIcon() { + m_upload_ev.show(); +} + +void ChatInputTextContainer::HideChooserIcon() { + m_upload_ev.hide(); +} + bool ChatInputTextContainer::GetChildPosition(Gtk::Widget *child, Gdk::Rectangle &pos) { Gtk::Allocation main_alloc; { @@ -470,6 +478,8 @@ ChatInput::ChatInput() m_attachments.signal_emptied().connect([this] { m_attachments_revealer.set_reveal_child(false); }); + + SetActiveChannel(Snowflake::Invalid); } void ChatInput::InsertText(const Glib::ustring &text) { @@ -526,6 +536,13 @@ void ChatInput::IndicateTooLarge() { void ChatInput::SetActiveChannel(Snowflake id) { m_active_channel = id; + if (CanAttachFiles()) { + m_input.Get().get_style_context()->add_class("with-browse-icon"); + m_input.ShowChooserIcon(); + } else { + m_input.Get().get_style_context()->remove_class("with-browse-icon"); + m_input.HideChooserIcon(); + } } void ChatInput::StartReplying() { diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index f6c4358..807f958 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -103,6 +103,9 @@ public: // not proxying everythign lol!! ChatInputText &Get(); + void ShowChooserIcon(); + void HideChooserIcon(); + private: void ShowFileChooser(); bool GetChildPosition(Gtk::Widget *child, Gdk::Rectangle &pos); -- cgit v1.2.3 From 02741f2c1beb499b188f6840c5384eb9c4f06c6d Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 8 Aug 2022 02:41:54 -0400 Subject: remove unnecessary verbosity --- src/abaddon.cpp | 11 +---------- src/http.cpp | 1 - 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index ce3bdb3..563a42c 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -744,22 +744,13 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) { }); } -static void ChatMessageSentCallback(const ChatSubmitParams &data) { - printf("completed for %s\n", data.Message.c_str()); - for (const auto &attachment : data.Attachments) { - puts(attachment.File->get_path().c_str()); - } -} - 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 (!m_discord.HasChannelPermission(m_discord.GetUserData().ID, data.ChannelID, Permission::VIEW_CHANNEL)) return; - m_discord.SendChatMessage(data, [data](DiscordError code) { - ChatMessageSentCallback(data); - }); + m_discord.SendChatMessage(data, NOOP_CALLBACK); } void Abaddon::ActionChatEditMessage(Snowflake channel_id, Snowflake id) { diff --git a/src/http.cpp b/src/http.cpp index f4d45a2..ba3ce3c 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -129,7 +129,6 @@ void request::add_file(std::string_view name, const Glib::RefPtr &fil // copied void request::add_field(std::string_view name, const char *data, size_t size) { - puts(name.data()); auto *field = curl_mime_addpart(m_form); curl_mime_name(field, name.data()); curl_mime_data(field, data, size); -- cgit v1.2.3 From f60cea2216dd9677cb9105364cdaa778a0c187db Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 10 Aug 2022 00:03:04 -0400 Subject: control icon pos in css --- res/css/main.css | 2 ++ src/components/chatinput.cpp | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/res/css/main.css b/res/css/main.css index 9398d48..ace3b0b 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -120,6 +120,8 @@ .message-input-browse-icon { color: #b9bbbe; + margin-left: 5px; + margin-top: 11px; } /* i dont think theres a way to circumvent having to do this to adjust around the browse icon */ diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 87892e0..2466965 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -176,11 +176,10 @@ bool ChatInputTextContainer::GetChildPosition(Gtk::Widget *child, Gdk::Rectangle Gtk::Requisition min, req; child->get_preferred_size(min, req); - // yummy hardcoded values - pos.set_x(5); + // let css move it around + pos.set_x(0); + pos.set_y(0); pos.set_width(std::max(min.width, std::min(main_alloc.get_width(), req.width))); - - pos.set_y(12); pos.set_height(std::max(min.height, std::min(main_alloc.get_height(), req.height))); return true; -- cgit v1.2.3