From 2a4c54ad5370f24242ea5cb7dcbdd11da1a46178 Mon Sep 17 00:00:00 2001 From: hiimgoodpack Date: Fri, 26 Mar 2021 12:06:08 -0400 Subject: [PATCH] Add more features --- README.md | 16 +++++++++ brainlet.h | 57 +++++++++++++++++++++++++++---- design.glade | 94 +++++++++++++++++++++++++++++++++++++++++++--------- main.cpp | 16 +++++++-- 4 files changed, 158 insertions(+), 25 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..4ec0e19 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# brainlet-client +The better client for Brainlet + +# Dependencies +- Linux (`linux`) +- gtkmm3 (`gtkmm3`) +- make (`make`) +- ccache (`ccache`) +- clang (`clang`) +- libcurl (`curl`) +- websocketpp (`websocketpp`) +- libjsoncpp (`jsoncpp`) + +# Features +- Chatting +- Markup using the [Pango markup language](https://developer.gnome.org/pygtk/stable/pango-markup-language.html) \ No newline at end of file diff --git a/brainlet.h b/brainlet.h index c7a441a..ce33560 100644 --- a/brainlet.h +++ b/brainlet.h @@ -56,7 +56,7 @@ namespace Brainlet { std::string color; ///< Color represented in #rrggbb } User; - class Client : private User { + class Client : public User { private: enum State { NOT_CONNECTED, @@ -73,6 +73,7 @@ namespace Brainlet { State state = NOT_CONNECTED; static void onReceive(Client* client, std::string buffer); + void encode(std::string& string); ///< Encodes a string to a JSON value public: std::function onNewChannel = nullptr; @@ -81,8 +82,8 @@ namespace Brainlet { Client(const std::string& domain); void processNextEvent(); - void login(const std::string& name, const std::string& password); - void sendMessage(const std::string& message, const std::string& channelId); + void login(std::string name, std::string password); + void sendMessage(std::string message, std::string channelId); }; } @@ -103,6 +104,7 @@ namespace Brainlet { #include #include // std;:strtol #include // std::for_each +#include // std::move #include // strerror #include @@ -110,6 +112,25 @@ namespace Brainlet { namespace Brainlet { Client::Client(const std::string& domain) : domain(domain) {} + void Client::encode(std::string& string) { + std::string result; + result.reserve(string.size()); + + std::for_each(string.begin(), string.end(), [&](const char character) { + switch (character) { + case '"': result.append("\\\""); break; + case '\\': result.append("\\\\"); break; + case '\b': result.append("\\b"); break; + case '\f': result.append("\\f"); break; + case '\n': result.append("\\n"); break; + case '\r': result.append("\\r"); break; + case '\t': result.append("\\t"); break; + default: result.push_back(character); break; + } + }); + + string = std::move(result); + } void Client::processNextEvent() { socket->run_until( // Stop in 250 microseconds @@ -117,10 +138,30 @@ namespace Brainlet { std::chrono::microseconds(250) ); } - void Client::login(const std::string& name, const std::string& password) { + void Client::login(std::string name, std::string password) { + { + // Escape the name and password + auto escape = [](std::string& string) { + std::string result; + result.reserve(string.size()); + + std::for_each(string.begin(), string.end(), [&](const char character) { + switch (character) { + case '%': result.append("%25"); break; + case '&': result.append("%26"); break; + default: result.push_back(character); break; + } + }); + + string = std::move(result); + }; + + escape(name); + escape(password); + } + Http::Request request("http://" + domain + "/api/v1/users/token/create"); - // TODO: Escape & const std::string postFields = "username="+name+"&password="+password+"&alsoSetCookie=true"; request.setPostFields(postFields); request.perform(); @@ -216,6 +257,7 @@ namespace Brainlet { }; if (client->onNewMessage) client->onNewMessage(message, channel); + break; } default: { std::cerr << "Unknown opcode " << opcode << ". Buffer " << buffer << "\n"; @@ -225,9 +267,10 @@ namespace Brainlet { } } } - void Client::sendMessage(const std::string& message, const std::string& channelId) { + void Client::sendMessage(std::string message, std::string channelId) { + encode(message); + std::ostringstream bufferToSend; - // TODO: Escape " from message bufferToSend << ACTION_CREATE_MESSAGE << "@" << R"({"content":")" << message << R"(","channel":{"_id":")" << channelId << R"("}})"; socket->send(bufferToSend.str()); } diff --git a/design.glade b/design.glade index 11781b6..e6a6524 100644 --- a/design.glade +++ b/design.glade @@ -24,26 +24,72 @@ SOFTWARE. True True - + + True - True - 5 - 5 - 5 - 5 + False + True True - in - + True - False + True + 5 + 5 + 5 + 5 + True + True + in - + True False + + + True + False + + + + 0 + 0 + 2 + + + + + True + False + 5 + 5 + 5 + 5 + [Username goes here] + + + 0 + 1 + + + + + True + False + GDK_BUTTON_PRESS_MASK + 5 + 5 + 5 + 5 + applications-system-symbolic.symbolic + + + 1 + 1 + @@ -56,6 +102,7 @@ SOFTWARE. True False + True True @@ -76,6 +123,7 @@ SOFTWARE. True False + none True @@ -86,6 +134,7 @@ SOFTWARE. False No channel has been opened. Select a channel on the box to the left. + True @@ -106,12 +155,10 @@ Select a channel on the box to the left. True True - 5 - 5 - 5 - 5 - True - Message + 2000 + Message (2000 character max) + GTK_INPUT_HINT_SPELLCHECK | GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_EMOJI | GTK_INPUT_HINT_NONE + True 0 @@ -132,6 +179,21 @@ Select a channel on the box to the left. ok An error has occured [Error message goes here] + + + False + + + False + + + False + False + 0 + + + + diff --git a/main.cpp b/main.cpp index 14df3a4..e3f81cd 100644 --- a/main.cpp +++ b/main.cpp @@ -45,6 +45,7 @@ Gtk::Widget* chatInterface; Gtk::ListBox* channelList; Gtk::ListBox* messageList; Gtk::Entry* messageEntry; +Gtk::Label* usernameLabel; gboolean update(gpointer) { client->processNextEvent(); @@ -56,6 +57,7 @@ void addChannel(const Brainlet::Channel channel) { auto name = Gtk::make_managed(); name->set_halign(Gtk::ALIGN_START); + name->set_line_wrap(true); name->set_text(channel.name); row->add(*name); @@ -76,13 +78,18 @@ void addMessage(const Brainlet::Message message) { auto text = Gtk::make_managed(); text->set_halign(Gtk::ALIGN_START); + text->set_line_wrap(true); std::string messageFormatted = message.authorId + ": " + message.message; - text->set_text(messageFormatted.c_str()); + text->set_markup(messageFormatted.c_str()); row->add(*text); messageList->add(*row); + std::string mention = "@" + client->id; + if (message.message.find(mention) != std::string::npos) + messageList->select_row(*row); + row->show_all_children(); row->show(); } @@ -105,14 +112,19 @@ int main(int argc, char* argv[]) { builder->get_widget("Channels", channelList); builder->get_widget("Messages", messageList); builder->get_widget("Message", messageEntry); + builder->get_widget("UsernameLabel", usernameLabel); auto login = [&]() { if (client.get() != nullptr) return; + + const std::string username = usernameEntry->get_text(); + const std::string password = passwordEntry->get_text(); + usernameLabel->set_text(username.c_str()); window->remove(); window->add(*chatInterface); client.reset(new Brainlet::Client(domainEntry->get_text())); - client->login(usernameEntry->get_text(), passwordEntry->get_text()); + client->login(username, password); client->onNewChannel = addChannel; client->onNewMessage = [&](const Brainlet::Message message, const Brainlet::Channel channel) {