summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorouwou <26526779+ouwou@users.noreply.github.com>2022-06-05 21:41:57 -0400
committerouwou <26526779+ouwou@users.noreply.github.com>2022-06-05 21:41:57 -0400
commit270730d9b36c8fc3a221da7e565632c1432d41c6 (patch)
treeed67d9fcec4df6287f3dd13cb997297d6af50ec8
parent4ec5c1dfccaa5ce3bce5dc22a95e91d781262345 (diff)
downloadabaddon-portaudio-270730d9b36c8fc3a221da7e565632c1432d41c6.tar.gz
abaddon-portaudio-270730d9b36c8fc3a221da7e565632c1432d41c6.zip
start attachments (image paste and upload)
-rw-r--r--src/abaddon.cpp6
-rw-r--r--src/abaddon.hpp2
-rw-r--r--src/components/chatinput.cpp204
-rw-r--r--src/components/chatinput.hpp89
-rw-r--r--src/components/chatlist.cpp4
-rw-r--r--src/components/chatlist.hpp3
-rw-r--r--src/components/chatwindow.cpp11
-rw-r--r--src/components/chatwindow.hpp4
-rw-r--r--src/constants.hpp1
-rw-r--r--src/discord/discord.cpp55
-rw-r--r--src/discord/discord.hpp7
-rw-r--r--src/discord/httpclient.cpp19
-rw-r--r--src/discord/httpclient.hpp3
-rw-r--r--src/http.cpp45
-rw-r--r--src/http.hpp15
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<std::string> &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<std::string> &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 <filesystem>
+#include <utility>
-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<Gtk::TextBuffer> ChatInput::GetBuffer() {
+Glib::RefPtr<Gtk::TextBuffer> 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<char, L_tmpnam> 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<Gdk::Pixbuf> &pb, const std::string &path) {
+ if (m_attachments.size() == 10) return false;
+
+ auto *item = Gtk::make_managed<ChatInputAttachmentItem>(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<std::string> ChatInputAttachmentContainer::GetFilePaths() const {
+ std::vector<std::string> 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<Gdk::Pixbuf> &pb)
+ : m_path(std::move(path))
+ , m_img(Gtk::make_managed<Gtk::Image>()) {
+ 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<GdkEvent *>(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<Gdk::Pixbuf> &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<GdkEvent *>(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<Gtk::TextBuffer> 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 <gtkmm.h>
-class ChatInput : public Gtk::ScrolledWindow {
+class ChatInputAttachmentItem : public Gtk::EventBox {
public:
- ChatInput();
+ ChatInputAttachmentItem(std::string path, const Glib::RefPtr<Gdk::Pixbuf> &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<void>;
+
+ 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<Gdk::Pixbuf> &pb, const std::string &path);
+ [[nodiscard]] std::vector<std::string> GetFilePaths() const;
+
+private:
+ std::set<ChatInputAttachmentItem *> m_attachments;
+
+ Gtk::Box m_box;
+
+private:
+ using type_signal_emptied = sigc::signal<void>;
+
+ 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<Gtk::TextBuffer> GetBuffer();
@@ -15,9 +63,42 @@ protected:
private:
Gtk::TextView m_textview;
+ bool CheckHandleClipboardPaste();
+ void HandleNewPastedImage(const Glib::RefPtr<Gdk::Pixbuf> &pb, const std::string &filename);
+
+public:
+ using type_signal_submit = sigc::signal<bool, Glib::ustring>;
+ using type_signal_escape = sigc::signal<void>;
+ using type_signal_image_paste = sigc::signal<void, Glib::RefPtr<Gdk::Pixbuf>, 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<Gtk::TextBuffer> GetBuffer();
+ bool ProcessKeyPress(GdkEventKey *event);
+
+private:
+ Gtk::Revealer m_attachments_revealer;
+ ChatInputAttachmentContainer m_attachments;
+ ChatInputText m_input;
+
public:
- typedef sigc::signal<bool, Glib::ustring> type_signal_submit;
- typedef sigc::signal<void> 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<bool, Glib::ustring, std::vector<std::string>>;
+ using type_signal_escape = sigc::signal<void>;
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<void, Snowflake, Snowflake>;
- using type_signal_action_chat_submit = sigc::signal<void, std::string, Snowflake, Snowflake>;
using type_signal_action_chat_load_history = sigc::signal<void, Snowflake>;
using type_signal_action_channel_click = sigc::signal<void, Snowflake>;
using type_signal_action_insert_mention = sigc::signal<void, Snowflake>;
@@ -73,7 +72,6 @@ public:
using type_signal_action_reply_to = sigc::signal<void, Snowflake>;
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<std::string> &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<std::string> &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<void, Snowflake, Snowflake>;
- using type_signal_action_chat_submit = sigc::signal<void, std::string, Snowflake, Snowflake>;
+ using type_signal_action_chat_submit = sigc::signal<void, std::string, std::vector<std::string>, Snowflake, Snowflake>;
using type_signal_action_chat_load_history = sigc::signal<void, Snowflake>;
using type_signal_action_channel_click = sigc::signal<void, Snowflake, bool>;
using type_signal_action_insert_mention = sigc::signal<void, Snowflake>;
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<std::string> &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<std::string> &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<std::string> &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<std::string> &attachment_paths, Snowflake channel);
+ void SendChatMessage(const std::string &content, const std::vector<std::string> &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<uint8_t> m_decompress_buf;
z_stream m_zstream;
+ void SendChatMessageAttachments(const std::string &content, const std::vector<std::string> &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<void(http:
}));
}
+http::request HTTPClient::CreateRequest(http::EMethod method, std::string path) {
+ http::request req(method, m_api_base + path);
+ req.set_header("Authorization", m_authorization);
+ req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
+#ifdef USE_LOCAL_PROXY
+ req.set_proxy("http://127.0.0.1:8888");
+ req.set_verify_ssl(false);
+#endif
+ return req;
+}
+
+void HTTPClient::Execute(http::request &&req, const std::function<void(http::response_type r)> &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<void(http::response_type r)> &cb);
void MakePUT(const std::string &path, const std::string &payload, const std::function<void(http::response_type r)> &cb);
+ [[nodiscard]] http::request CreateRequest(http::EMethod method, std::string path);
+ void Execute(http::request &&req, const std::function<void(http::response_type r)> &cb);
+
private:
void OnResponse(const http::response_type &r, const std::function<void(http::response_type r)> &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 <array>
#include <string>
#include <curl/curl.h>
@@ -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<char, CURL_ERROR_SIZE> m_error_buf = { 0 };
+ curl_mime *m_form = nullptr;
};
using response_type = response;