From 06f30543730c372226c398c11b3de0213d711d13 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 8 Aug 2018 23:17:10 +0200 Subject: Add support for discord --- src/Cache.cpp | 51 ++++++- src/Channel.cpp | 86 +++++++++++- src/ChannelTopPanel.cpp | 2 +- src/Chatbar.cpp | 14 ++ src/MessageBoard.cpp | 51 +++++-- src/ResourceCache.cpp | 24 ++++ src/Rpc.cpp | 39 ++++++ src/Suggestions.cpp | 54 ++++++++ src/Text.cpp | 55 ++++---- src/User.cpp | 25 +++- src/UsersSidePanel.cpp | 126 ++++++++--------- src/main.cpp | 361 ++++++++++++++++++++++++++++++++++++++++++++++-- 12 files changed, 753 insertions(+), 135 deletions(-) create mode 100644 src/Rpc.cpp create mode 100644 src/Suggestions.cpp (limited to 'src') 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 #include #include +#include #if OS_FAMILY == OS_FAMILY_POSIX #include @@ -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::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::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 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 Channel::getUsers() const + const vector& 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(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& 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 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& 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 +#include 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 +#include + +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 + +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 &_texts) + { + for(const auto &text : _texts) + { + sf::String str = sf::String::fromUtf8(text.begin(), text.end()); + texts.emplace_back(std::make_unique(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 +#include #include -#include 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(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 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 #include #include +#include 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(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(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(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 +#include #include #include #include @@ -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(); @@ -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(); + u8 discordNameLength = deserializer.extract(); + 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; + 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 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(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 &args) + Command::add("channelname", [&offlineChannel, addSystemMessage](const vector &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(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 myDiscordIdsByChannel; + + bool running = true; + thread rpcThread([&running, &onMessageByLocalUser, &channels, &lastFocusedTimer, &myDiscordIdsByChannel]() + { + Rpc rpc(5555); + mutex messageMutex; + vector messagesToSend; + onMessageByLocalUser = [&messagesToSend, &messageMutex](const odhtdb::DatabaseAddNodeRequest &request) + { + lock_guard lock(messageMutex); + auto channelDataType = (ChannelDataType)static_cast(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 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 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 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) { -- cgit v1.2.3