forked from hiimgoodpack/brainlet-client
Add more features
This commit is contained in:
parent
859e1b2835
commit
2a4c54ad53
4 changed files with 158 additions and 25 deletions
16
README.md
Normal file
16
README.md
Normal file
|
@ -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)
|
57
brainlet.h
57
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<void(Channel)> 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 <cassert>
|
||||
#include <cstdlib> // std;:strtol
|
||||
#include <algorithm> // std::for_each
|
||||
#include <utility> // std::move
|
||||
#include <string.h> // strerror
|
||||
|
||||
#include <json/json.h>
|
||||
|
@ -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());
|
||||
}
|
||||
|
|
94
design.glade
94
design.glade
|
@ -24,26 +24,72 @@ SOFTWARE.
|
|||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<!-- n-columns=2 n-rows=2 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="Channels">
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="Channels">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="UsernameLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="label" translatable="yes">[Username goes here]</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage" id="SettingsButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="events">GDK_BUTTON_PRESS_MASK</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="icon-name">applications-system-symbolic.symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -56,6 +102,7 @@ SOFTWARE.
|
|||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
|
@ -76,6 +123,7 @@ SOFTWARE.
|
|||
<object class="GtkListBox" id="Messages">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<child>
|
||||
<object class="GtkListBoxRow">
|
||||
<property name="visible">True</property>
|
||||
|
@ -86,6 +134,7 @@ SOFTWARE.
|
|||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">No channel has been opened.
|
||||
Select a channel on the box to the left.</property>
|
||||
<property name="wrap">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -106,12 +155,10 @@ Select a channel on the box to the left.</property>
|
|||
<object class="GtkEntry" id="Message">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="placeholder-text" translatable="yes">Message</property>
|
||||
<property name="max-length">2000</property>
|
||||
<property name="placeholder-text" translatable="yes">Message (2000 character max)</property>
|
||||
<property name="input-hints">GTK_INPUT_HINT_SPELLCHECK | GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_EMOJI | GTK_INPUT_HINT_NONE</property>
|
||||
<property name="show-emoji-icon">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
|
@ -132,6 +179,21 @@ Select a channel on the box to the left.</property>
|
|||
<property name="buttons">ok</property>
|
||||
<property name="text" translatable="yes">An error has occured</property>
|
||||
<property name="secondary-text" translatable="yes">[Error message goes here]</property>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can-focus">False</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<!-- n-columns=3 n-rows=5 -->
|
||||
<object class="GtkGrid" id="Login">
|
||||
|
|
16
main.cpp
16
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<Gtk::Label>();
|
||||
|
||||
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<Gtk::Label>();
|
||||
|
||||
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) {
|
||||
|
|
Reference in a new issue