aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2018-08-08 23:17:10 +0200
committerdec05eba <dec05eba@protonmail.com>2018-08-08 23:17:14 +0200
commit06f30543730c372226c398c11b3de0213d711d13 (patch)
treed6229ff224a9e7e5457c856747c2f8726e7a3868 /src
parent26edc563cb0ba1a9fb35507e7f32d2d43a845e68 (diff)
Add support for discord
Diffstat (limited to 'src')
-rw-r--r--src/Cache.cpp51
-rw-r--r--src/Channel.cpp86
-rw-r--r--src/ChannelTopPanel.cpp2
-rw-r--r--src/Chatbar.cpp14
-rw-r--r--src/MessageBoard.cpp51
-rw-r--r--src/ResourceCache.cpp24
-rw-r--r--src/Rpc.cpp39
-rw-r--r--src/Suggestions.cpp54
-rw-r--r--src/Text.cpp55
-rw-r--r--src/User.cpp25
-rw-r--r--src/UsersSidePanel.cpp126
-rw-r--r--src/main.cpp361
12 files changed, 753 insertions, 135 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 2a09591..77ba515 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -14,6 +14,7 @@
#include <sibs/SafeSerializer.hpp>
#include <sibs/SafeDeserializer.hpp>
#include <libpreview.h>
+#include <gd.h>
#if OS_FAMILY == OS_FAMILY_POSIX
#include <pwd.h>
@@ -119,8 +120,42 @@ namespace dchat
}
fileReplace(getDchatDir() / "binds", StringView((const char*)serializer.getBuffer().data(), serializer.getBuffer().size()));
}
+
+ static bool downscaleImage(const boost::filesystem::path &filepath, void *data, const int size, const int newWidth, const int newHeight)
+ {
+ gdImagePtr imgPtr = gdImageCreateFromPngPtr(size, data);
+ if(!imgPtr)
+ return false;
+
+ int width = gdImageSX(imgPtr);
+ if(width < newWidth)
+ {
+ gdImageDestroy(imgPtr);
+ return false;
+ }
+
+ int height = gdImageSX(imgPtr);
+ if(height < newHeight)
+ {
+ gdImageDestroy(imgPtr);
+ return false;
+ }
+
+ gdImageSetInterpolationMethod(imgPtr, GD_BILINEAR_FIXED);
+ gdImagePtr newImgPtr = gdImageScale(imgPtr, newWidth, newHeight);
+ if(!newImgPtr)
+ {
+ gdImageDestroy(imgPtr);
+ return false;
+ }
+
+ bool success = (gdImageFile(newImgPtr, filepath.c_str()) == 0);
+ gdImageDestroy(imgPtr);
+ gdImageDestroy(newImgPtr);
+ return success;
+ }
- static ContentByUrlResult loadImageFromFile(const boost::filesystem::path &filepath)
+ static ContentByUrlResult loadImageFromFile(const boost::filesystem::path &filepath, bool loadFromCache)
{
StringView fileContent;
try
@@ -147,8 +182,16 @@ namespace dchat
}
else
{
+ if(!loadFromCache)
+ {
+ if(!downscaleImage(filepath, (void*)fileContent.data, fileContent.size, 100, 100))
+ {
+ fprintf(stderr, "Failed to resize image: %s, using original file\n", filepath.c_str());
+ }
+ }
+
sf::Texture *texture = new sf::Texture();
- if(texture->loadFromMemory(fileContent.data, fileContent.size))
+ if(texture->loadFromFile(filepath.c_str()))
{
delete[] fileContent.data;
fileContent.data = nullptr;
@@ -213,7 +256,7 @@ namespace dchat
}
else
{
- contentByUrlResult = loadImageFromFile(filepath);
+ contentByUrlResult = loadImageFromFile(filepath, false);
contentByUrlResult.lastAccessed = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED)
{
@@ -350,7 +393,7 @@ namespace dchat
}
// TODO: Do not load content in this thread. Return LOADING status and load it in another thread, because with a lot of images, chat can freeze
- ContentByUrlResult contentByUrlResult = loadImageFromFile(filepath);
+ ContentByUrlResult contentByUrlResult = loadImageFromFile(filepath, true);
if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED)
{
contentByUrlResult.lastAccessed = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
diff --git a/src/Channel.cpp b/src/Channel.cpp
index 75d805c..d13c476 100644
--- a/src/Channel.cpp
+++ b/src/Channel.cpp
@@ -17,12 +17,25 @@ namespace dchat
messageBoard(this),
localUser(_localUser ? _localUser : new OfflineUser("You"))
{
+ bridgeServices.push_back(new DiscordService());
addUserLocally(localUser);
//addLocalMessage(u8"[emoji](https://discordemoji.com/assets/emoji/PepeDab.gif) deaf [emoji](https://discordemoji.com/assets/emoji/COGGERS.gif)", &systemUser, 0, odhtdb::Hash());
//addLocalMessage(u8"[emoji](https://discordemoji.com/assets/emoji/PepeDab.gif)[emoji](https://discordemoji.com/assets/emoji/COGGERS.gif)", &systemUser, 0, odhtdb::Hash());
//addLocalMessage(u8"pepedab https://discordemoji.com/assets/emoji/PepeDab.gif coggers https://discordemoji.com/assets/emoji/COGGERS.gif check out this url http://www.grandtournation.com/6808/start-date-of-the-grand-tour-season-3-confirmed-mark-your-calendars/ owo", &systemUser, 0, odhtdb::Hash());
- // addLocalMessage(u8"ht clic", &systemUser, 0, odhtdb::Hash());
+ //addLocalMessage(u8"ht clic", &systemUser, 0, odhtdb::Hash());
+
+ auto binds = Chatbar::getBinds();
+ vector<string> suggestionsStr;
+ suggestionsStr.reserve(binds.size());
+ for(auto &bind : binds)
+ {
+ string suggestion = bind.first;
+ suggestion += " ";
+ suggestion += bind.second;
+ suggestionsStr.emplace_back(move(suggestion));
+ }
+ suggestions.show(suggestionsStr);
if(database)
{
@@ -67,6 +80,11 @@ namespace dchat
Channel::~Channel()
{
+ for(BridgeService *bridgeService : bridgeServices)
+ {
+ delete bridgeService;
+ }
+
if(database)
{
database->cancelNodeListener(pingKey, pingListener);
@@ -78,6 +96,11 @@ namespace dchat
{
delete user;
}
+
+ for(auto &discordUserIt : discordUserById)
+ {
+ delete discordUserIt.second;
+ }
}
User* Channel::getLocalUser()
@@ -100,7 +123,7 @@ namespace dchat
return name;
}
- const vector<User*> Channel::getUsers() const
+ const vector<User*>& Channel::getUsers() const
{
return users;
}
@@ -137,6 +160,31 @@ namespace dchat
}
messageBoard.addMessage(new Message(owner, msg, timestampSeconds), id);
}
+
+ void Channel::addLocalDiscordMessage(const string &discordUserName, u64 discordUserId, const string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id)
+ {
+ assert(owner);
+ if(timestampSeconds == 0)
+ {
+ timestampSeconds = time(NULL);
+ }
+
+ OnlineDiscordUser *discordUser = nullptr;
+ auto discordUserIt = discordUserById.find(discordUserId);
+ if(discordUserIt == discordUserById.end())
+ {
+ discordUser = new OnlineDiscordUser(discordUserName, discordUserId, owner);
+ discordUserById[discordUserId] = discordUser;
+ }
+ else
+ {
+ // TODO: What if several users bridge same chat? the same discord user id could belong to different owners.
+ // Dchat channels should only allow one user to bridge data from one discord channel. Bridging data between multiple discord channels to
+ // one dchat channel should be allowed.
+ discordUser = discordUserIt->second;
+ }
+ messageBoard.addMessage(new Message(discordUser, msg, timestampSeconds), id);
+ }
void Channel::addSystemMessage(const string &msg, bool plainText)
{
@@ -159,6 +207,21 @@ namespace dchat
else
addLocalMessage(msg, localUser, 0, odhtdb::Hash());
}
+
+ void Channel::addDiscordMessage(const string &discordUserName, u64 discordUserId, const string &msg)
+ {
+ assert(database && localUser->type == User::Type::ONLINE_LOCAL_USER);
+ auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
+
+ sibs::SafeSerializer serializer;
+ serializer.add(ChannelDataType::ADD_DISCORD_MESSAGE);
+ serializer.add(discordUserId);
+ serializer.add((u8)discordUserName.size());
+ serializer.add((const u8*)discordUserName.data(), discordUserName.size());
+ serializer.add((const u8*)msg.data(), msg.size());
+
+ database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
+ }
void Channel::deleteLocalMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser)
{
@@ -282,6 +345,12 @@ namespace dchat
database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
}
}
+
+ int Channel::getUserLowestPermissionLevel(OnlineUser *user) const
+ {
+ if(!database) return -1;
+ return database->getUserLowestPermissionLevel(*databaseNodeInfo.getRequestHash(), user->getPublicKey());
+ }
void Channel::processEvent(const sf::Event &event, Cache &cache)
{
@@ -293,6 +362,7 @@ namespace dchat
{
messageBoard.draw(window, cache);
chatbar.draw(window, cache);
+ //suggestions.draw(window, cache);
}
void Channel::update()
@@ -300,7 +370,7 @@ namespace dchat
if(database && localUser->type == User::Type::ONLINE_LOCAL_USER && pingTimer.getElapsedTime().asMilliseconds() > 5000)
{
pingTimer.restart();
- sendPing(database->getSyncedTimestampUtc().seconds);
+ //sendPing(database->getSyncedTimestampUtc().seconds);
}
}
@@ -327,6 +397,16 @@ namespace dchat
return 0;
return database->getSyncedTimestampUtc().seconds;
}
+
+ const vector<BridgeService*>& Channel::getBridgeServices() const
+ {
+ return bridgeServices;
+ }
+
+ DiscordService* Channel::getDiscordService()
+ {
+ return (DiscordService*)bridgeServices[0];
+ }
void Channel::setCurrent(Channel *channel)
{
diff --git a/src/ChannelTopPanel.cpp b/src/ChannelTopPanel.cpp
index bf122d2..9d4869e 100644
--- a/src/ChannelTopPanel.cpp
+++ b/src/ChannelTopPanel.cpp
@@ -45,7 +45,7 @@ namespace dchat
const sf::Color lineCenterColor = lineSideColor + sf::Color(40, 0, 0);
auto windowSize = window.getSize();
- sf::RectangleShape rect(sf::Vector2f(floor(windowSize.x - ChannelSidePanel::getWidth() - UsersSidePanel::getWidth()), getHeight() - BOTTOM_LINE_HEIGHT));
+ sf::RectangleShape rect(sf::Vector2f(floor(windowSize.x - ChannelSidePanel::getWidth()), getHeight() - BOTTOM_LINE_HEIGHT));
rect.setPosition(ChannelSidePanel::getWidth(), 0.0f);
rect.setFillColor(ColorScheme::getBackgroundColor());
window.draw(rect);
diff --git a/src/Chatbar.cpp b/src/Chatbar.cpp
index bb06308..a3e1d0f 100644
--- a/src/Chatbar.cpp
+++ b/src/Chatbar.cpp
@@ -217,6 +217,20 @@ namespace dchat
window.draw(inputBackground);
text.draw(window, cache);
}
+
+ sf::Vector2f Chatbar::getInputPosition(sf::RenderWindow &window)
+ {
+ auto windowSize = window.getSize();
+ return { floor(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), floor(windowSize.y - getInputSize(window).y - PADDING_BOTTOM * Settings::getScaling()) };
+ }
+
+ sf::Vector2f Chatbar::getInputSize(sf::RenderWindow &window)
+ {
+ auto windowSize = window.getSize();
+ const float fontSize = FONT_SIZE * Settings::getScaling();
+ const float fontHeight = ResourceCache::getFont("fonts/Nunito-Regular.ttf")->getLineSpacing(fontSize);
+ return { floor(windowSize.x - ChannelSidePanel::getWidth() - UsersSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), floor(fontHeight * 1.7f + BOX_PADDING_Y * Settings::getScaling() * 2.0f) };
+ }
float Chatbar::getHeight()
{
diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp
index aa92d46..2c89be3 100644
--- a/src/MessageBoard.cpp
+++ b/src/MessageBoard.cpp
@@ -36,7 +36,7 @@ namespace dchat
const float TEXT_LINE_SPACING = 5.0f;
const float USERNAME_TIMESTAMP_SIDE_PADDING = 15.0f;
const float AVATAR_DIAMETER = 70.0f;
- const float AVATAR_PADDING_SIDE = 30.0f;
+ const float AVATAR_PADDING_SIDE = 20.0f;
const double SCROLL_MAX_SPEED = 20.0;
// Merge messages from same user that are sent within one minute
@@ -52,7 +52,7 @@ namespace dchat
visibleMessageEndIndex(-1)
{
scrollbar.backgroundColor = sf::Color(49, 52, 57);
- scrollbar.scrollColor = sf::Color(42, 44, 49);
+ scrollbar.scrollColor = sf::Color(37, 39, 44);
}
MessageBoard::~MessageBoard()
@@ -81,16 +81,24 @@ namespace dchat
dirty = true;
}
- void MessageBoard::addMessage(Message *message, const odhtdb::Hash &id)
+ bool MessageBoard::addMessage(Message *message, const odhtdb::Hash &id)
{
lock_guard<mutex> lock(messageProcessMutex);
+ bool emptyHash = id.isEmpty();
+ if(!emptyHash && messageIdMap.find(id) != messageIdMap.end())
+ {
+ delete message;
+ return false;
+ }
auto positionToAddMessage = findPositionToInsertMessageByTimestamp(message);
if(positionToAddMessage == messages.size())
scrollToBottom = true;
messages.insert(messages.begin() + positionToAddMessage, message);
message->id = id;
- messageIdMap[id] = message;
+ if(!emptyHash)
+ messageIdMap[id] = message;
dirty = true;
+ return true;
}
void MessageBoard::deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser)
@@ -167,6 +175,8 @@ namespace dchat
}
bool visible = false;
+
+ float startX = floor(position.x + LINE_SIDE_PADDING * Settings::getScaling() - PADDING_SIDE * Settings::getScaling());
if(!mergeTextWithPrev)
{
@@ -174,9 +184,19 @@ namespace dchat
if(position.y + usernameTextHeight > 0.0f && position.y < backgroundPos.y + backgroundSize.y)
{
visible = true;
- sf::Text usernameText(sf::String::fromUtf8(message->user->getName().begin(), message->user->getName().end()), *usernameFont, usernameTextCharacterSize);
+ string usernameStr;
+ if(message->user->type == User::Type::ONLINE_DISCORD_USER)
+ {
+ usernameStr = "(Discord) ";
+ usernameStr += message->user->getName();
+ }
+ else
+ {
+ usernameStr = message->user->getName();
+ }
+ sf::Text usernameText(sf::String::fromUtf8(usernameStr.begin(), usernameStr.end()), *usernameFont, usernameTextCharacterSize);
usernameText.setFillColor(sf::Color(15, 192, 252));
- usernameText.setPosition(sf::Vector2f(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y)));
+ usernameText.setPosition(sf::Vector2f(floor(startX + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y)));
window.draw(usernameText);
if(message->timestampSeconds)
@@ -188,7 +208,7 @@ namespace dchat
sf::Text timestamp(date, *timestampFont, timestampTextCharacterSize);
timestamp.setFillColor(ColorScheme::getTextRegularColor() * sf::Color(255, 255, 255, 50));
- timestamp.setPosition(sf::Vector2f(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling() + usernameText.getLocalBounds().width + USERNAME_TIMESTAMP_SIDE_PADDING * Settings::getScaling()), floor(position.y + 2.0f * Settings::getScaling() + usernameTextHeight * 0.5f - timestampTextHeight * 0.5f)));
+ timestamp.setPosition(sf::Vector2f(floor(startX + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling() + usernameText.getLocalBounds().width + USERNAME_TIMESTAMP_SIDE_PADDING * Settings::getScaling()), floor(position.y + 2.0f * Settings::getScaling() + usernameTextHeight * 0.5f - timestampTextHeight * 0.5f)));
window.draw(timestamp);
}
@@ -203,14 +223,14 @@ namespace dchat
// TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame
sf::Sprite sprite(*avatarResult.texture);
auto textureSize = avatarResult.texture->getSize();
- sprite.setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
+ sprite.setPosition(sf::Vector2f(startX, floor(position.y)));
sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.y));
window.draw(sprite, circleShader);
}
else if(avatarResult.cachedType == ContentByUrlResult::CachedType::GIF)
{
auto gifSize = avatarResult.gif->getSize();
- avatarResult.gif->setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
+ avatarResult.gif->setPosition(sf::Vector2f(startX, floor(position.y)));
avatarResult.gif->setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y));
avatarResult.gif->setColor(sf::Color::White);
avatarResult.gif->draw(window, circleShader);
@@ -219,7 +239,7 @@ namespace dchat
else
{
sf::CircleShape avatarCircle(AVATAR_DIAMETER * 0.5f * Settings::getScaling(), 60 * Settings::getScaling());
- avatarCircle.setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
+ avatarCircle.setPosition(sf::Vector2f(startX, floor(position.y)));
avatarCircle.setFillColor(ColorScheme::getBackgroundColor() + sf::Color(30, 30, 30));
window.draw(avatarCircle);
}
@@ -230,8 +250,8 @@ namespace dchat
// No need to perform culling here, that is done in @Text draw function
message->text.setCharacterSize(18.0f * Settings::getScaling());
- message->text.setMaxWidth(backgroundSize.x - (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling());
- message->text.setPosition(sf::Vector2f(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y)));
+ message->text.setMaxWidth(lineRect.getSize().x - (AVATAR_DIAMETER + AVATAR_PADDING_SIDE * 2.0f) * Settings::getScaling());
+ message->text.setPosition(sf::Vector2f(floor(startX + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y)));
bool textDrawn = message->text.draw(window, cache);
if(!visible)
visible = textDrawn;
@@ -243,7 +263,7 @@ namespace dchat
if(position.y + LINE_HEIGHT > 0.0f && position.y < backgroundPos.y + backgroundSize.y && i + 1 != numMessages)
{
visible = true;
- lineRect.setPosition(sf::Vector2f(position.x + LINE_SIDE_PADDING * Settings::getScaling() - PADDING_SIDE * Settings::getScaling(), floor(position.y)));
+ lineRect.setPosition(sf::Vector2f(startX, floor(position.y)));
window.draw(lineRect);
//drawGradientLine(sf::Vector2f(position.x + LINE_SIDE_PADDING, floor(position.y)), sf::Vector2f(backgroundSizeWithoutPadding.x - LINE_SIDE_PADDING * 2.0f, LINE_HEIGHT), LINE_COLOR, window);
}
@@ -459,4 +479,9 @@ namespace dchat
return messages.back();
return nullptr;
}
+
+ const std::vector<Message*>& MessageBoard::getMessages() const
+ {
+ return messages;
+ }
}
diff --git a/src/ResourceCache.cpp b/src/ResourceCache.cpp
index 4664bf0..f6f8fad 100644
--- a/src/ResourceCache.cpp
+++ b/src/ResourceCache.cpp
@@ -1,5 +1,6 @@
#include "../include/ResourceCache.hpp"
#include <unordered_map>
+#include <gd.h>
using namespace std;
@@ -33,6 +34,29 @@ namespace dchat
auto it = textures.find(filepath);
if(it != textures.end())
return it->second;
+
+ gdImagePtr imgPtr = gdImageCreateFromFile(filepath.c_str());
+ if(!imgPtr)
+ {
+ string errMsg = "Failed to load texture with gd: ";
+ errMsg += filepath;
+ throw FailedToLoadResourceException(errMsg);
+ }
+
+ gdImageSetInterpolationMethod(imgPtr, GD_BILINEAR_FIXED);
+ gdImagePtr newImgPtr = gdImageScale(imgPtr, 100, 100);
+ if(!newImgPtr)
+ {
+ gdImageDestroy(imgPtr);
+ string errMsg = "Failed to scale image with gd: ";
+ errMsg += filepath;
+ throw FailedToLoadResourceException(errMsg);
+ }
+
+ gdImageFile(newImgPtr, filepath.c_str());
+
+ gdImageDestroy(imgPtr);
+ gdImageDestroy(newImgPtr);
sf::Texture *texture = new sf::Texture();
if(!texture->loadFromFile(filepath))
diff --git a/src/Rpc.cpp b/src/Rpc.cpp
new file mode 100644
index 0000000..2bf54fb
--- /dev/null
+++ b/src/Rpc.cpp
@@ -0,0 +1,39 @@
+#include "../include/Rpc.hpp"
+#include <string>
+#include <cassert>
+
+namespace dchat
+{
+ Rpc::Rpc(u16 port) :
+ context(1),
+ socket(context, ZMQ_PAIR)
+ {
+ std::string addr = "tcp://*:";
+ addr += std::to_string(port);
+ socket.bind(addr);
+ }
+
+ void Rpc::recv(RpcRecvCallbackFunc recvCallbackFunc)
+ {
+ assert(recvCallbackFunc);
+ zmq::message_t request;
+ if(socket.recv(&request, ZMQ_NOBLOCK))
+ {
+ recvCallbackFunc(&request);
+ }
+ }
+
+ bool Rpc::send(const void *data, const usize size)
+ {
+ if(size == 0) return false;
+ try
+ {
+ return socket.send(data, size, ZMQ_NOBLOCK) > 0;
+ }
+ catch(zmq::error_t &e)
+ {
+ fprintf(stderr, "Rpc::send failed, reason: %s\n", e.what());
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Suggestions.cpp b/src/Suggestions.cpp
new file mode 100644
index 0000000..113cd7e
--- /dev/null
+++ b/src/Suggestions.cpp
@@ -0,0 +1,54 @@
+#include "../include/Suggestions.hpp"
+#include "../include/Text.hpp"
+#include "../include/ResourceCache.hpp"
+#include "../include/Settings.hpp"
+#include "../include/ColorScheme.hpp"
+#include "../include/Chatbar.hpp"
+#include <cmath>
+
+namespace dchat
+{
+ static const char *FONT_PATH = "fonts/Nunito-Regular.ttf";
+ static float FONT_SCALING = 18.0f;
+
+ static sf::Vector2f floor(const sf::Vector2f &vec)
+ {
+ return { std::floor(vec.x), std::floor(vec.y) };
+ }
+
+ void Suggestions::show(const std::vector<std::string> &_texts)
+ {
+ for(const auto &text : _texts)
+ {
+ sf::String str = sf::String::fromUtf8(text.begin(), text.end());
+ texts.emplace_back(std::make_unique<Text>(str, ResourceCache::getFont(FONT_PATH), FONT_SCALING * Settings::getScaling(), 0.0f, false));
+ }
+ }
+
+ void Suggestions::draw(sf::RenderWindow &window, Cache &cache)
+ {
+ if(texts.empty()) return;
+
+ sf::Vector2f position = Chatbar::getInputPosition(window);
+ sf::Vector2f size = Chatbar::getInputSize(window);
+ size.y = FONT_SCALING * Settings::getScaling() * (1 + texts.size()) * 1.7f;
+ position.y -= size.y;
+
+ position = floor(position);
+ size = floor(size);
+
+ sf::RectangleShape rect(size);
+ rect.setPosition(position);
+ rect.setFillColor(ColorScheme::getPanelColor());
+ window.draw(rect);
+
+ for(const auto &text : texts)
+ {
+ text->setCharacterSize(FONT_SCALING * Settings::getScaling());
+ text->setMaxWidth(size.x);
+ text->setPosition(position.x, std::floor(position.y));
+ text->draw(window, cache);
+ position.y += text->getHeight();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Text.cpp b/src/Text.cpp
index c8ee731..39e339f 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -6,8 +6,8 @@
#include "../include/ImagePreview.hpp"
#include "../include/StringUtils.hpp"
#include <SFML/Graphics/RectangleShape.hpp>
+#include <SFML/Window/Clipboard.hpp>
#include <cmath>
-#include <process.hpp>
namespace dchat
{
@@ -341,6 +341,7 @@ namespace dchat
sf::Uint32 prevCodePoint = 0;
size_t lastSpacingWordWrapIndex = -1;
float lastSpacingAccumulatedOffset = 0.0f;
+ bool lineHasEmoji = false;
for(usize textElementIndex = 0; textElementIndex < textElements.size(); ++textElementIndex)
{
TextElement &textElement = textElements[textElementIndex];
@@ -377,11 +378,12 @@ namespace dchat
{
textElement.position.y = glyphPos.y;
// TODO: Find a better way to do this, @totalHeight is wrong because we add emojiSize and then vspace
- glyphPos.y += floor(emojiSize - vspace);
+ glyphPos.y += floor(emojiSize - vspace) + lineSpacing;
}
else
{
textElement.position.y = glyphPos.y + vspace * 0.5f - emojiSize * 0.5f;
+ lineHasEmoji = true;
}
glyphPos.x += emojiSize + EMOJI_PADDING + characterSpacing;
if(glyphPos.x > maxWidth)
@@ -443,16 +445,13 @@ namespace dchat
vertices[vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
vertices[vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
glyphPos.x = 0.0f;
- glyphPos.y += floor(vspace + lineSpacing);
- continue;
- }
- case '\v':
- {
- vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 1] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
- glyphPos.y += floor(vspace * TAB_WIDTH + lineSpacing);
+ if(lineHasEmoji)
+ {
+ lineHasEmoji = false;
+ glyphPos.y += floor(vspace * EMOJI_SCALE_WITH_TEXT + lineSpacing);
+ }
+ else
+ glyphPos.y += floor(vspace + lineSpacing);
continue;
}
}
@@ -481,7 +480,13 @@ namespace dchat
else
glyphPos.x = 0.0f;
- glyphPos.y += floor(vspace + lineSpacing);
+ if(lineHasEmoji)
+ {
+ lineHasEmoji = false;
+ glyphPos.y += floor(vspace * EMOJI_SCALE_WITH_TEXT + lineSpacing);
+ }
+ else
+ glyphPos.y += floor(vspace + lineSpacing);
}
sf::Vector2f vertexTopLeft(glyphPos.x + glyph.bounds.left, glyphPos.y + glyph.bounds.top);
@@ -513,7 +518,7 @@ namespace dchat
if(textElement.type == TextElement::Type::URL)
{
- glyphPos.y += vspace + lineSpacing;
+ glyphPos.y += floor(vspace + lineSpacing);
textElement.position.y = glyphPos.y;
glyphPos.x = 0.0f;
@@ -521,7 +526,12 @@ namespace dchat
}
}
- boundingBox.height = glyphPos.y + vspace + lineSpacing;
+ boundingBox.height = glyphPos.y + lineSpacing;
+ if(lineHasEmoji)
+ boundingBox.height += vspace * EMOJI_SCALE_WITH_TEXT;
+ else
+ boundingBox.height += vspace;
+
usize numVertices = vertices.getVertexCount();
for(usize i = 0; i < numVertices; i += 4)
{
@@ -708,18 +718,6 @@ namespace dchat
return static_cast<int>(1.0f + position.y / (vspace + lineSpacing));
}
- static std::string getClipboard()
- {
- std::string result;
- TinyProcessLib::Process process("xsel -o -b", "", [&result](const char *bytes, size_t n)
- {
- result.append(bytes, n);
- });
- if(process.get_exit_status() != 0)
- fprintf(stderr, "Failed to get clipboard content\n");
- return result;
- }
-
void Text::onMouseClick(const sf::Event::MouseButtonEvent &event, Cache &cache)
{
if(event.button != sf::Mouse::Button::Left) return;
@@ -869,8 +867,7 @@ namespace dchat
sf::String stringToAdd;
if(event.text.unicode == 22) // ctrl+v
{
- auto clipboardString = getClipboard();
- stringToAdd = sf::String::fromUtf8(clipboardString.begin(), clipboardString.end());
+ stringToAdd = sf::Clipboard::getString();
}
else if(event.text.unicode >= 32 || event.text.unicode == 9) // 9 == tab
stringToAdd = event.text.unicode;
diff --git a/src/User.cpp b/src/User.cpp
index aabfd0b..510f884 100644
--- a/src/User.cpp
+++ b/src/User.cpp
@@ -1,8 +1,10 @@
#include "../include/User.hpp"
+#include <cassert>
namespace dchat
{
const static std::string SYSTEM_USER_NAME = "System";
+ const i64 USER_TIMEOUT_SEC = 25;
User::User(Type _type) :
type(_type)
@@ -17,12 +19,18 @@ namespace dchat
{
}
-
+
const std::string& OnlineUser::getName() const
{
return name;
}
+ bool OnlineUser::isConnected(i64 timestampUtcSec) const
+ {
+ i64 pingTimeDiffSec = timestampUtcSec - (i64)pingTimestampSec;
+ return pingTimeDiffSec <= USER_TIMEOUT_SEC;
+ }
+
OnlineRemoteUser::OnlineRemoteUser(const std::string &name, const odhtdb::Signature::PublicKey &_publicKey) :
OnlineUser(name, Type::ONLINE_REMOTE_USER),
publicKey(_publicKey)
@@ -41,11 +49,24 @@ namespace dchat
{
}
-
+
const odhtdb::Signature::PublicKey& OnlineLocalUser::getPublicKey() const
{
return keyPair.getPublicKey();
}
+
+ OnlineDiscordUser::OnlineDiscordUser(const std::string &discordUserName, u64 _discordUserId, User *_bridgeOwner) :
+ OnlineUser(discordUserName, Type::ONLINE_DISCORD_USER),
+ discordUserId(_discordUserId),
+ bridgeOwner(_bridgeOwner)
+ {
+ assert(bridgeOwner);
+ }
+
+ const odhtdb::Signature::PublicKey& OnlineDiscordUser::getPublicKey() const
+ {
+ return odhtdb::Signature::PublicKey::ZERO;
+ }
OfflineUser::OfflineUser(const std::string &_name) :
User(Type::OFFLINE),
diff --git a/src/UsersSidePanel.cpp b/src/UsersSidePanel.cpp
index 687b976..b1f7843 100644
--- a/src/UsersSidePanel.cpp
+++ b/src/UsersSidePanel.cpp
@@ -11,6 +11,7 @@
#include <SFML/Graphics/Text.hpp>
#include <vector>
#include <cmath>
+#include <sibs/Functional.hpp>
using namespace std;
@@ -24,47 +25,50 @@ namespace dchat
const float PADDING_BOTTOM = 20.0f;
const i64 USER_TIMEOUT_SEC = 25;
- static void renderUser(Cache &cache, User *user, sf::Shader *circleShader, sf::RenderWindow &window, sf::Vector2f &position, const sf::Font *font, const float textHeight, bool isUserOnline)
+ static void renderUser(Cache &cache, const User *user, sf::Shader *circleShader, sf::RenderWindow &window, sf::Vector2f &position, const sf::Font *font, const float textHeight, bool isUserOnline)
{
- // Max avatar size = 1mb
- const ContentByUrlResult avatarResult = cache.getContentByUrl(user->avatarUrl, 1024 * 1024);
- if(avatarResult.type == ContentByUrlResult::Type::CACHED)
+ if(position.y + AVATAR_DIAMETER > 0.0f && position.y < window.getSize().y)
{
- circleShader->setUniform("texture", sf::Shader::CurrentTexture);
-
- if(avatarResult.cachedType == ContentByUrlResult::CachedType::TEXTURE)
+ // Max avatar size = 1mb
+ const ContentByUrlResult avatarResult = cache.getContentByUrl(user->avatarUrl, 1024 * 1024);
+ if(avatarResult.type == ContentByUrlResult::Type::CACHED)
{
- // TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame
- sf::Sprite sprite(*avatarResult.texture);
- auto textureSize = avatarResult.texture->getSize();
- sprite.setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
- sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.y));
- sprite.setColor(isUserOnline ? sf::Color::White : sf::Color(255, 255, 255, 100));
- window.draw(sprite, circleShader);
+ circleShader->setUniform("texture", sf::Shader::CurrentTexture);
+
+ if(avatarResult.cachedType == ContentByUrlResult::CachedType::TEXTURE)
+ {
+ // TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame
+ sf::Sprite sprite(*avatarResult.texture);
+ auto textureSize = avatarResult.texture->getSize();
+ sprite.setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
+ sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.y));
+ sprite.setColor(isUserOnline ? sf::Color::White : sf::Color(255, 255, 255, 100));
+ window.draw(sprite, circleShader);
+ }
+ else if(avatarResult.cachedType == ContentByUrlResult::CachedType::GIF)
+ {
+ auto gifSize = avatarResult.gif->getSize();
+ avatarResult.gif->setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
+ avatarResult.gif->setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y));
+ avatarResult.gif->setColor(isUserOnline ? sf::Color::White : sf::Color(255, 255, 255, 100));
+ avatarResult.gif->draw(window, circleShader);
+ }
}
- else if(avatarResult.cachedType == ContentByUrlResult::CachedType::GIF)
+ else
{
- auto gifSize = avatarResult.gif->getSize();
- avatarResult.gif->setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
- avatarResult.gif->setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y));
- avatarResult.gif->setColor(isUserOnline ? sf::Color::White : sf::Color(255, 255, 255, 100));
- avatarResult.gif->draw(window, circleShader);
+ sf::CircleShape avatarCircle(AVATAR_DIAMETER * 0.5f * Settings::getScaling(), 60 * Settings::getScaling());
+ avatarCircle.setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
+ avatarCircle.setFillColor(isUserOnline ? ColorScheme::getBackgroundColor() + sf::Color(30, 30, 30) : ColorScheme::getBackgroundColor() * sf::Color(255, 255, 255, 100));
+ window.draw(avatarCircle);
}
+
+ // TODO: Remove this shit
+ sf::String str = sf::String::fromUtf8(user->getName().begin(), user->getName().end());
+ sf::Text text(str, *font, FONT_SIZE * Settings::getScaling());
+ text.setPosition(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y + AVATAR_DIAMETER * 0.5f - textHeight * 0.5f));
+ text.setFillColor(isUserOnline ? sf::Color(15, 192, 252) : sf::Color(15, 192, 252, 100));
+ window.draw(text);
}
- else
- {
- sf::CircleShape avatarCircle(AVATAR_DIAMETER * 0.5f * Settings::getScaling(), 60 * Settings::getScaling());
- avatarCircle.setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
- avatarCircle.setFillColor(isUserOnline ? ColorScheme::getBackgroundColor() + sf::Color(30, 30, 30) : ColorScheme::getBackgroundColor() * sf::Color(255, 255, 255, 100));
- window.draw(avatarCircle);
- }
-
- // TODO: Remove this shit
- sf::String str = sf::String::fromUtf8(user->getName().begin(), user->getName().end());
- sf::Text text(str, *font, FONT_SIZE * Settings::getScaling());
- text.setPosition(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y + AVATAR_DIAMETER * 0.5f - textHeight * 0.5f));
- text.setFillColor(isUserOnline ? sf::Color(15, 192, 252) : sf::Color(15, 192, 252, 100));
- window.draw(text);
position.y += ((AVATAR_DIAMETER + PADDING_BOTTOM) * Settings::getScaling());
}
@@ -86,24 +90,22 @@ namespace dchat
const float textHeight = font->getLineSpacing(FONT_SIZE * Settings::getScaling());
i64 timestampSec = currentChannel->getSyncedTimestampUtcInSec();
+ auto &channelUsers = currentChannel->getUsers();
+ auto currentChannelUsers = sibs::makeFunction(channelUsers.data(), channelUsers.data() + channelUsers.size());
+ for(BridgeService *bridgeService : currentChannel->getBridgeServices())
+ {
+ auto &bridgeServiceUsers = bridgeService->getUsers();
+ currentChannelUsers.merge(sibs::makeFunction(bridgeServiceUsers.data(), bridgeServiceUsers.data() + bridgeServiceUsers.size()));
+ }
u32 numOnlineUsers = 0;
u32 numOfflineUsers = 0;
- for(User *user : currentChannel->getUsers())
+ for(const User *user : currentChannelUsers)
{
- bool hasUserTimedOut = false;
- if(user->isOnlineUser())
- {
- auto onlineUser = static_cast<OnlineUser*>(user);
- i64 pingTimeDiffSec = timestampSec - (i64)onlineUser->pingTimestampSec;
- if(pingTimeDiffSec > USER_TIMEOUT_SEC)
- hasUserTimedOut = true;
- }
-
- if(hasUserTimedOut)
- ++numOfflineUsers;
- else
+ if(user->isConnected(timestampSec))
++numOnlineUsers;
+ else
+ ++numOfflineUsers;
}
// TODO: Remove this shit
@@ -118,19 +120,10 @@ namespace dchat
sf::Shader *circleShader = ResourceCache::getShader("shaders/circleMask.glsl", sf::Shader::Fragment);
- for(User *user : currentChannel->getUsers())
+ for(const User *user : currentChannelUsers)
{
- bool isUserOnline = true;
- if(user->isOnlineUser())
- {
- auto onlineUser = static_cast<OnlineUser*>(user);
- i64 pingTimeDiffSec = timestampSec - (i64)onlineUser->pingTimestampSec;
- if(pingTimeDiffSec > USER_TIMEOUT_SEC)
- isUserOnline = false;
- }
-
- if(isUserOnline)
- renderUser(cache, user, circleShader, window, position, font, textHeight, isUserOnline);
+ if(user->isConnected(timestampSec))
+ renderUser(cache, user, circleShader, window, position, font, textHeight, true);
}
if(numOfflineUsers == 0) return;
@@ -146,19 +139,10 @@ namespace dchat
position.y += floor(font->getLineSpacing(text.getCharacterSize()));
position.y += PADDING_BOTTOM * Settings::getScaling() * 0.5f;
- for(User *user : currentChannel->getUsers())
+ for(const User *user : currentChannelUsers)
{
- bool isUserOnline = true;
- if(user->isOnlineUser())
- {
- auto onlineUser = static_cast<OnlineUser*>(user);
- i64 pingTimeDiffSec = timestampSec - (i64)onlineUser->pingTimestampSec;
- if(pingTimeDiffSec > USER_TIMEOUT_SEC)
- isUserOnline = false;
- }
-
- if(!isUserOnline)
- renderUser(cache, user, circleShader, window, position, font, textHeight, isUserOnline);
+ if(!user->isConnected(timestampSec))
+ renderUser(cache, user, circleShader, window, position, font, textHeight, false);
}
}
diff --git a/src/main.cpp b/src/main.cpp
index 0836278..efb354f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -11,6 +11,9 @@
#include "../include/GlobalContextMenu.hpp"
#include "../include/StringUtils.hpp"
#include "../include/ImagePreview.hpp"
+#include "../include/Rpc.hpp"
+#include <msgpack.hpp>
+#include <sstream>
#include <string>
#include <SFML/Graphics.hpp>
#include <cstring>
@@ -94,6 +97,13 @@ static void channelChangeChannelName(Channel *channel, const StringView data, co
fprintf(stderr, "Channel change name: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str());
return;
}
+
+ int userPermissionLevel = channel->getUserLowestPermissionLevel(user);
+ if(userPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN)
+ {
+ fprintf(stderr, "Channel change name: attempted by user %s who is not an admin\n", user->getName().c_str());
+ return;
+ }
sibs::SafeDeserializer deserializer((const u8*)data.data, data.size);
u16 channelNameLength = deserializer.extract<u16>();
@@ -107,6 +117,40 @@ static void channelChangeChannelName(Channel *channel, const StringView data, co
// We dont care if there is more data to read (malicious packet), we already got all the data we need
}
+static void channelAddDiscordMessage(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey, u64 timestamp, const odhtdb::Hash &requestHash)
+{
+ auto bridgeOwner = channel->getUserByPublicKey(userPublicKey);
+ if(!bridgeOwner)
+ {
+ fprintf(stderr, "Channel add discord message: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str());
+ return;
+ }
+
+ int userPermissionLevel = channel->getUserLowestPermissionLevel(bridgeOwner);
+ if(userPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN)
+ {
+ fprintf(stderr, "Channel add discord message: attempted by user %s who is not an admin\n", bridgeOwner->getName().c_str());
+ return;
+ }
+
+ sibs::SafeDeserializer deserializer((const u8*)data.data, data.size);
+ u64 discordUserId = deserializer.extract<u64>();
+ u8 discordNameLength = deserializer.extract<u8>();
+ if(discordNameLength == 0) return;
+
+ string discordUserName;
+ discordUserName.resize(discordNameLength);
+ deserializer.extract((u8*)&discordUserName[0], discordNameLength);
+
+ usize msgSize = deserializer.getSize();
+ if(msgSize == 0) return;
+ string msg(deserializer.getBuffer(), deserializer.getBuffer() + deserializer.getSize());
+
+ auto timestampSeconds = ntp::NtpTimestamp::fromCombined(timestamp).seconds;
+ channel->addLocalDiscordMessage(discordUserName, discordUserId, msg, bridgeOwner, timestampSeconds, requestHash);
+ // We dont care if there is more data to read (malicious packet), we already got all the data we need
+}
+
static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &requestHash, const odhtdb::Signature::PublicKey &creatorPublicKey, const StringView decryptedObject, u64 timestamp, bool loadedFromCache)
{
User *user = channel->getUserByPublicKey(creatorPublicKey);
@@ -187,6 +231,18 @@ static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &reques
}
break;
}
+ case ChannelDataType::ADD_DISCORD_MESSAGE:
+ {
+ try
+ {
+ channelAddDiscordMessage(channel, decryptedData, creatorPublicKey, timestamp, requestHash);
+ }
+ catch(sibs::DeserializeException &e)
+ {
+ fprintf(stderr, "Failed to deserialize channel add discord message\n");
+ }
+ break;
+ }
default:
fprintf(stderr, "Got unexpected channel data type: %u\n", channelDataType);
break;
@@ -207,7 +263,7 @@ int main(int argc, char **argv)
printf("Resource path set to: %s\n", resourcesPath.string().c_str());
}
else
- printf("Resource directory not defined, using currently directory");
+ printf("Resource directory not defined, using currently directory\n");
const sf::Int64 FRAMERATE_FOCUSED = 144;
const sf::Int64 FRAMERATE_NOT_FOCUSED = 10;
@@ -239,6 +295,9 @@ int main(int argc, char **argv)
odhtdb::Database *database = nullptr;
odhtdb::DatabaseCallbackFuncs callbackFuncs;
+
+ using LocalUserMessageCallback = function<void(const odhtdb::DatabaseAddNodeRequest &request)>;
+ LocalUserMessageCallback onMessageByLocalUser = nullptr;
callbackFuncs.createNodeCallbackFunc = [&waitingToJoinChannels, &database, &channels, &channelMessageMutex, &waitingToJoin, &localNodeUsers, &lastFocusedTimer](const odhtdb::DatabaseCreateNodeRequest &request)
{
@@ -280,7 +339,7 @@ int main(int argc, char **argv)
}
};
- callbackFuncs.addNodeCallbackFunc = [&channels, &channelMessageMutex, &lastFocusedTimer](const odhtdb::DatabaseAddNodeRequest &request)
+ callbackFuncs.addNodeCallbackFunc = [&channels, &channelMessageMutex, &lastFocusedTimer, &onMessageByLocalUser](const odhtdb::DatabaseAddNodeRequest &request)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
//printf("Add node callback func %s\n", request.requestHash->toString().c_str());
@@ -291,6 +350,12 @@ int main(int argc, char **argv)
channelAddStoredMessage(channel, *request.requestHash, *request.creatorPublicKey, StringView((const char*)request.decryptedData.data, request.decryptedData.size), request.timestamp, request.loadedFromCache);
if(channel == Channel::getCurrent())
lastFocusedTimer.restart();
+
+ if(!request.loadedFromCache && *request.creatorPublicKey == static_cast<OnlineLocalUser*>(channel->getLocalUser())->getPublicKey())
+ {
+ if(onMessageByLocalUser)
+ onMessageByLocalUser(request);
+ }
return;
}
}
@@ -808,7 +873,7 @@ int main(int argc, char **argv)
});
// Change name of the current channel
- Command::add("channelname", [&loggedIn, &offlineChannel, addSystemMessage](const vector<string> &args)
+ Command::add("channelname", [&offlineChannel, addSystemMessage](const vector<string> &args)
{
if(args.size() != 1)
{
@@ -818,16 +883,17 @@ int main(int argc, char **argv)
addSystemMessage(errMsg);
return;
}
-
- if(!loggedIn)
+
+ Channel *currentChannel = Channel::getCurrent();
+ if(currentChannel == &offlineChannel)
{
- addSystemMessage("You need to be logged in to change channel name");
+ addSystemMessage("You need to be in a channel to change channel name");
return;
}
- if(Channel::getCurrent() == &offlineChannel)
+ if(!currentChannel->getLocalUser()->isOnlineUser())
{
- addSystemMessage("You need to be in a channel to change channel name");
+ addSystemMessage("You need to be logged in to change channel name");
return;
}
@@ -836,8 +902,15 @@ int main(int argc, char **argv)
addSystemMessage("Channel name has to be between 1 and 32 bytes long");
return;
}
+
+ int localUserPermissionLevel = currentChannel->getUserLowestPermissionLevel(static_cast<OnlineLocalUser*>(currentChannel->getLocalUser()));
+ if(localUserPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN)
+ {
+ addSystemMessage("You need to be admin to change channel name");
+ return;
+ }
- Channel::getCurrent()->setName(args[0]);
+ currentChannel->setName(args[0]);
string msg = "Channel name has been changed to ";
msg += args[0];
addSystemMessage(msg);
@@ -855,10 +928,268 @@ int main(int argc, char **argv)
commandsMsg += commandIt.first;
}
addSystemMessage(commandsMsg);
+
+ odhtdb::MapHash<u64> myDiscordIdsByChannel;
+
+ bool running = true;
+ thread rpcThread([&running, &onMessageByLocalUser, &channels, &lastFocusedTimer, &myDiscordIdsByChannel]()
+ {
+ Rpc rpc(5555);
+ mutex messageMutex;
+ vector<msgpack::sbuffer> messagesToSend;
+ onMessageByLocalUser = [&messagesToSend, &messageMutex](const odhtdb::DatabaseAddNodeRequest &request)
+ {
+ lock_guard<mutex> lock(messageMutex);
+ auto channelDataType = (ChannelDataType)static_cast<const char*>(request.decryptedData.data)[0];
+ usize size = request.decryptedData.size - 1;
+ const char *data = (const char*)request.decryptedData.data + 1;
+ const char *action = nullptr;
+ if(channelDataType == ChannelDataType::ADD_MESSAGE)
+ action = "addMessage";
+
+ if(!action) return;
+ vector<string> msg = { action, string(data, data + size), request.nodeHash->toString() };
+ msgpack::sbuffer buffer;
+ msgpack::pack(buffer, msg);
+ messagesToSend.emplace_back(move(buffer));
+ };
+
+ while(running)
+ {
+ rpc.recv([&channels, &lastFocusedTimer, &myDiscordIdsByChannel](zmq::message_t *message)
+ {
+ try
+ {
+ msgpack::object_handle oh = msgpack::unpack((const char*)message->data(), message->size());
+ auto deserialized = oh.get();
+ vector<string> msg;
+ deserialized.convert(msg);
+ if(msg.size() < 2)
+ {
+ fprintf(stderr, "Rpc receive, data length expected to be at least 2, was %u\n", msg.size());
+ return;
+ }
+ auto &action = msg[0];
+
+ string dchatChannelIdRaw = odhtdb::hex2bin(msg[1].c_str(), msg[1].size());
+ odhtdb::Hash dchatChannelId;
+ memcpy(dchatChannelId.getData(), dchatChannelIdRaw.data(), dchatChannelIdRaw.size());
+ Channel *bridgedChannel = nullptr;
+ for(Channel *channel : channels)
+ {
+ if(*channel->getNodeInfo().getRequestHash() == dchatChannelId)
+ {
+ bridgedChannel = channel;
+ break;
+ }
+ }
+
+ if(!bridgedChannel)
+ {
+ fprintf(stderr, "Rcp addMessage, invalid dchat channel %s\n", msg[1].c_str());
+ return;
+ }
+
+ if(bridgedChannel == Channel::getCurrent())
+ lastFocusedTimer.restart();
+
+ fprintf(stderr, "Received rpc, action: %s\n", action.c_str());
+ if(action == "addMessage")
+ {
+ if((msg.size() - 2) % 4 != 0)
+ {
+ fprintf(stderr, "Rpc addMessage, request was malformed\n");
+ return;
+ }
+
+ for(size_t i = 2; i < msg.size(); i += 4)
+ {
+ auto &content = msg[i];
+ auto &discordUserId = msg[i + 1];
+ u64 discordUserIdNumber = 0;
+ auto &discordUserName = msg[i + 2];
+ auto &messageTimestampMillisec = msg[i + 3];
+ u64 messageTimestampSecondsNumber = 0;
+
+ try
+ {
+ discordUserIdNumber = stoull(discordUserId);
+ }
+ catch(...)
+ {
+ fprintf(stderr, "Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str());
+ return;
+ }
+
+ try
+ {
+ messageTimestampSecondsNumber = stoull(messageTimestampMillisec) / 1000;
+ }
+ catch(...)
+ {
+ fprintf(stderr, "Rpc receive, failed to convert discord message timestamp to uint64_t: %s\n", messageTimestampMillisec.c_str());
+ return;
+ }
+
+ auto myDiscordIdIt = myDiscordIdsByChannel.find(dchatChannelId);
+ if(myDiscordIdIt != myDiscordIdsByChannel.end() && myDiscordIdIt->second == discordUserIdNumber)
+ {
+ auto &channelMessages = bridgedChannel->getMessageBoard().getMessages();
+ Message *myLatestMessage = nullptr;
+ for(auto it = channelMessages.rbegin(), end = channelMessages.rend(); it != end; ++it)
+ {
+ if((*it)->user == bridgedChannel->getLocalUser())
+ {
+ myLatestMessage = *it;
+ break;
+ }
+ }
+
+ if(myLatestMessage && (i64)messageTimestampSecondsNumber - (i64)myLatestMessage->timestampSeconds <= 3)
+ {
+ return;
+ /*
+ auto myMessageUtf8 = myLatestMessage->text.getString().toUtf8();
+ odhtdb::Hash myMessageHash(myMessageUtf8.data(), myMessageUtf8.size());
+ odhtdb::Hash myDiscordMessageHash(content.data(), content.size());
+ if(myMessageHash == myDiscordMessageHash)
+ return;
+ */
+ }
+ }
+
+ bridgedChannel->addLocalDiscordMessage(discordUserName, discordUserIdNumber, content, bridgedChannel->getLocalUser(), messageTimestampSecondsNumber, odhtdb::Hash(messageTimestampMillisec.c_str(), messageTimestampMillisec.size()));
+ }
+ }
+ else if(action == "addUser")
+ {
+ if((msg.size() - 2) % 4 != 0)
+ {
+ fprintf(stderr, "Rpc addUser, request was malformed\n");
+ return;
+ }
+
+ for(size_t i = 2; i < msg.size(); i += 4)
+ {
+ auto &discordUsername = msg[i];
+ auto &discordUserId = msg[i + 1];
+ auto &userStatus = msg[i + 2];
+ auto &avatarURL = msg[i + 3];
+
+ try
+ {
+ u64 discordUserIdNumber = stoull(discordUserId);
+ bool online = (userStatus != "offline");
+ printf("Rpc, adding user %s with status %s\n", discordUsername.c_str(), userStatus.c_str());
+ DiscordServiceUser *discordUser = bridgedChannel->getDiscordService()->getUserById(discordUserIdNumber);
+ if(discordUser)
+ {
+ discordUser->connected = online;
+ }
+ else
+ {
+ discordUser = new DiscordServiceUser(discordUsername, discordUserIdNumber, online);
+ bridgedChannel->getDiscordService()->addUser(discordUser);
+ }
+ discordUser->avatarUrl = avatarURL;
+ }
+ catch(...)
+ {
+ fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str());
+ // Ignore for now.. should we really handle this error other than showing warning?
+ }
+ }
+ }
+ else if(action == "removeUser")
+ {
+ for(size_t i = 2; i < msg.size(); ++i)
+ {
+ auto &discordUserId = msg[i];
+ try
+ {
+ u64 discordUserIdNumber = stoull(discordUserId);
+ bridgedChannel->getDiscordService()->removeUser(discordUserIdNumber);
+ }
+ catch(...)
+ {
+ fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str());
+ // Ignore for now.. should we really handle this error other than showing warning?
+ }
+ }
+ }
+ else if(action == "statusChange")
+ {
+ if((msg.size() - 2) != 2)
+ {
+ fprintf(stderr, "Rpc statusChange, request was malformed\n");
+ return;
+ }
+
+ auto &discordUserId = msg[2];
+ auto &userStatus = msg[3];
+
+ try
+ {
+ u64 discordUserIdNumber = stoull(discordUserId);
+ DiscordServiceUser *discordUser = bridgedChannel->getDiscordService()->getUserById(discordUserIdNumber);
+ if(discordUser)
+ {
+ discordUser->connected = (userStatus != "offline");
+ printf("Rcp statusChange, changed user %s (%s) status to %s\n", discordUserId.c_str(), discordUser->getName().c_str(), userStatus.c_str());
+ }
+ }
+ catch(...)
+ {
+ fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str());
+ // Ignore for now.. should we really handle this error other than showing warning?
+ }
+ }
+ else if(action == "addMe")
+ {
+ if((msg.size() - 2) != 1)
+ {
+ fprintf(stderr, "Rpc addMe, request was malformed\n");
+ return;
+ }
+
+ auto &myDiscordId = msg[2];
+ try
+ {
+ u64 myDiscordIdNumber = stoull(myDiscordId);
+ myDiscordIdsByChannel[dchatChannelId] = myDiscordIdNumber;
+ }
+ catch(...)
+ {
+ fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", myDiscordId.c_str());
+ // Ignore for now.. should we really handle this error other than showing warning?
+ }
+ }
+ else
+ {
+ fprintf(stderr, "Rcp received unknown action %s\n", action.c_str());
+ }
+ }
+ catch(msgpack::type_error &e)
+ {
+ fprintf(stderr, "Failed to deserialize received rpc, error: %s\n", e.what());
+ }
+ });
+ {
+ lock_guard<mutex> lock(messageMutex);
+ for(auto &messageToSend : messagesToSend)
+ {
+ fprintf(stderr, "Rpc, sending message\n");
+ rpc.send(messageToSend.data(), messageToSend.size());
+ }
+ messagesToSend.clear();
+ }
+ this_thread::sleep_for(chrono::milliseconds(50));
+ }
+ });
sf::Clock frameTimer;
- while (window.isOpen())
+ while (running)
{
frameTimer.restart();
Channel *currentChannel = Channel::getCurrent();
@@ -867,11 +1198,14 @@ int main(int argc, char **argv)
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
+ {
window.close();
+ running = false;
+ }
else if(event.type == sf::Event::Resized)
{
sf::FloatRect viewRect(0.0f, 0.0f, event.size.width, event.size.height);
- /* // TODO: Use xlib to set window minimum size instead
+ /* // TODO: Use xlib/xcb to set window minimum size instead
const int minWidth = 800;
if(event.size.width < minWidth)
{
@@ -916,7 +1250,7 @@ int main(int argc, char **argv)
channel->update();
}
- if(lastFocusedTimer.getElapsedTime().asMilliseconds() > 5000)
+ if((!windowFocused || !focused) && lastFocusedTimer.getElapsedTime().asMilliseconds() > 5000)
{
this_thread::sleep_for(chrono::milliseconds(250));
continue;
@@ -948,6 +1282,9 @@ int main(int argc, char **argv)
//video.draw(window);
window.display();
}
+
+ onMessageByLocalUser = nullptr;
+ rpcThread.join();
for(Channel *channel : channels)
{