#include "../include/Channel.hpp" #include "../include/ChannelSidePanel.hpp" #include "../include/UsersSidePanel.hpp" #include "../include/ChannelTopPanel.hpp" #include "../include/Cache.hpp" #include "../include/ResourceCache.hpp" #include "../include/Video.hpp" #include "../include/Command.hpp" #include "../include/Settings.hpp" #include "../include/ColorScheme.hpp" #include "../include/GlobalContextMenu.hpp" #include "../include/StringUtils.hpp" #include "../include/ImagePreview.hpp" #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace dchat; static bool focused = true; static void channelChangeUserNickname(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey) { auto user = channel->getUserByPublicKey(userPublicKey); if(!user) { fprintf(stderr, "Nickname change: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str()); return; } sibs::SafeDeserializer deserializer((const u8*)data.data, data.size); u8 nameLength = deserializer.extract(); if(nameLength > 0) { user->name.resize(nameLength); deserializer.extract((u8*)&user->name[0], nameLength); } // We dont care if there is more data to read (malicious packet), we already got all the data we need } static bool looksLikeUrl(const string &str) { if(str.find('.') == string::npos) return false; if(str.size() >= 7 && strncmp(str.c_str(), "http://", 7) == 0) return true; if(str.size() >= 8 && strncmp(str.c_str(), "https://", 8) == 0) return true; return false; } static void channelChangeUserAvatar(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey) { auto user = channel->getUserByPublicKey(userPublicKey); if(!user) { fprintf(stderr, "Avatar change: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str()); return; } sibs::SafeDeserializer deserializer((const u8*)data.data, data.size); u16 avatarLength = deserializer.extract(); if(avatarLength >= 10 && avatarLength <= 512) { string avatarUrl; avatarUrl.resize(avatarLength); deserializer.extract((u8*)&avatarUrl[0], avatarLength); if(looksLikeUrl(avatarUrl)) { user->avatarUrl = move(avatarUrl); } } // We dont care if there is more data to read (malicious packet), we already got all the data we need } static void channelChangeChannelName(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey) { auto user = channel->getUserByPublicKey(userPublicKey); if(!user) { 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; } sibs::SafeDeserializer deserializer((const u8*)data.data, data.size); u16 channelNameLength = deserializer.extract(); if(channelNameLength > 0 && channelNameLength <= 32) { string channelName; channelName.resize(channelNameLength); deserializer.extract((u8*)&channelName[0], channelNameLength); channel->setNameLocally(channelName); } // 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) { User *user = channel->getUserByPublicKey(creatorPublicKey); if(!user) { fprintf(stderr, "Missing user? %s\n", creatorPublicKey.toString().c_str()); return; } if(decryptedObject.size > 1) { auto channelDataType = (ChannelDataType)static_cast(decryptedObject.data)[0]; StringView decryptedData((const char*)decryptedObject.data + 1, decryptedObject.size - 1); switch(channelDataType) { case ChannelDataType::ADD_MESSAGE: { string msg(decryptedData.data, decryptedData.size); channel->addLocalMessage(msg, user, ntp::NtpTimestamp::fromCombined(timestamp).seconds, requestHash); if(!focused) { stringReplaceChar(msg, "'", ""); stringReplaceChar(msg, "\\", ""); string cmd = "notify-send dchat '"; cmd += msg; cmd += "'"; system(cmd.c_str()); } break; } case ChannelDataType::DELETE_MESSAGE: { sibs::SafeDeserializer deserializer((const u8*)decryptedData.data, decryptedData.size); odhtdb::Hash messageId; deserializer.extract((u8*)messageId.getData(), odhtdb::HASH_BYTE_SIZE); channel->deleteLocalMessage(messageId, creatorPublicKey); break; } case ChannelDataType::NICKNAME_CHANGE: { try { channelChangeUserNickname(channel, decryptedData, creatorPublicKey); } catch(sibs::DeserializeException &e) { fprintf(stderr, "Failed to deserialize nick change\n"); } break; } case ChannelDataType::CHANGE_AVATAR: { try { channelChangeUserAvatar(channel, decryptedData, creatorPublicKey); } catch(sibs::DeserializeException &e) { fprintf(stderr, "Failed to deserialize avatar change\n"); } break; } case ChannelDataType::CHANGE_CHANNEL_NAME: { try { channelChangeChannelName(channel, decryptedData, creatorPublicKey); } catch(sibs::DeserializeException &e) { fprintf(stderr, "Failed to deserialize channel name change\n"); } break; } default: fprintf(stderr, "Got unexpected channel data type: %u\n", channelDataType); break; } } else { fprintf(stderr, "ADD_DATA packet too small, ignoring...\n"); } } int main(int argc, char **argv) { /* boost::filesystem::path programPath(argv[0]); auto parentPath = programPath.parent_path(); printf("parent path: %s\n", parentPath.string().c_str()); boost::filesystem::current_path(parentPath); // Ensures loading of resources works no matter which path we run this executable from */ const sf::Int64 FRAMERATE_FOCUSED = 144; const sf::Int64 FRAMERATE_NOT_FOCUSED = 10; XInitThreads(); sf::RenderWindow window(sf::VideoMode(1280, 768), "dchat"); window.setVerticalSyncEnabled(false); window.setFramerateLimit(FRAMERATE_FOCUSED); //Video video(500, 500, "https://www.youtube.com/watch?v=bs0-EX9mJmg"); Cache cache; Cache::loadBindsFromFile(); Channel offlineChannel("Offline"); ChannelSidePanel::addChannel(&offlineChannel); Channel::setCurrent(&offlineChannel); vector channels; vector waitingToJoinChannels; odhtdb::MapHash localNodeUsers; string currentUsername; string currentPassword; recursive_mutex channelMessageMutex; bool waitingToJoin = false; bool loggedIn = false; sf::Clock lastFocusedTimer; odhtdb::Database *database = nullptr; odhtdb::DatabaseCallbackFuncs callbackFuncs; callbackFuncs.createNodeCallbackFunc = [&waitingToJoinChannels, &database, &channels, &channelMessageMutex, &waitingToJoin, &localNodeUsers, &lastFocusedTimer](const odhtdb::DatabaseCreateNodeRequest &request) { lock_guard lock(channelMessageMutex); //printf("Create node callback func %s\n", request.nodeHash->toString().c_str()); auto nodeUserData = localNodeUsers.find(*request.nodeHash); if(nodeUserData == localNodeUsers.end()) return; User *localUser; if(nodeUserData->second.userKeyPair->getPublicKey() == *request.creatorPublicKey) localUser = new OnlineLocalUser("NoName", *nodeUserData->second.userKeyPair); else localUser = new OfflineUser("You"); shared_ptr databaseNodeHash = make_shared(); memcpy(databaseNodeHash->getData(), request.nodeHash->getData(), odhtdb::HASH_BYTE_SIZE); odhtdb::DatabaseNode databaseNode(nodeUserData->second.nodeEncryptionKey, databaseNodeHash); Channel *channel = new Channel("NoChannelName", databaseNode, localUser, database); ChannelSidePanel::addChannel(channel); channels.push_back(channel); Channel::setCurrent(channel); lastFocusedTimer.restart(); if(localUser->type == User::Type::OFFLINE) { User *nodeCreatorUser = new OnlineRemoteUser("NoName", *request.creatorPublicKey); channel->addUserLocally(nodeCreatorUser); } for(vector::iterator it = waitingToJoinChannels.begin(); it != waitingToJoinChannels.end(); ++it) { if(*request.nodeHash == *it->getRequestHash()) { waitingToJoin = true; waitingToJoinChannels.erase(it); return; } } }; callbackFuncs.addNodeCallbackFunc = [&channels, &channelMessageMutex, &lastFocusedTimer](const odhtdb::DatabaseAddNodeRequest &request) { lock_guard lock(channelMessageMutex); //printf("Add node callback func %s\n", request.requestHash->toString().c_str()); for(Channel *channel : channels) { if(*request.nodeHash == *channel->getNodeInfo().getRequestHash()) { channelAddStoredMessage(channel, *request.requestHash, *request.creatorPublicKey, StringView((const char*)request.decryptedData.data, request.decryptedData.size), request.timestamp); if(channel == Channel::getCurrent()) lastFocusedTimer.restart(); return; } } }; callbackFuncs.addUserCallbackFunc = [&channels, &channelMessageMutex, &waitingToJoin, &localNodeUsers, &lastFocusedTimer](const odhtdb::DatabaseAddUserRequest &request) { lock_guard lock(channelMessageMutex); printf("Add user callback. Channel to add user to: %s\n", request.nodeHash->toString().c_str()); for(Channel *channel : channels) { printf("My channel: %s (%s)\n", channel->getNodeInfo().getRequestHash()->toString().c_str(), channel->getName().c_str()); if(*request.nodeHash == *channel->getNodeInfo().getRequestHash()) { printf("Add user to one of my channels\n"); auto nodeUserData = localNodeUsers.find(*request.nodeHash); User *userToAdd = channel->getUserByPublicKey(*request.userToAddPublicKey); if(userToAdd) { fprintf(stderr, "User %s already exists in channel\n", request.userToAddPublicKey->toString().c_str()); return; } if(channel == Channel::getCurrent()) lastFocusedTimer.restart(); if(*request.userToAddPublicKey == nodeUserData->second.userKeyPair->getPublicKey()) { printf("You were added to channel %s by %s\n", request.nodeHash->toString().c_str(), request.creatorPublicKey->toString().c_str()); channel->replaceLocalUser(new OnlineLocalUser("NoName", *nodeUserData->second.userKeyPair)); waitingToJoin = false; return; } User *newRemoteUser = new OnlineRemoteUser("NoName", *request.userToAddPublicKey); channel->addUserLocally(newRemoteUser); return; } } }; database = new odhtdb::Database("bootstrap.ring.cx", 4222, Cache::getDchatDir(), callbackFuncs); auto addSystemMessage = [&lastFocusedTimer](const std::string &msg, bool plainText = true) { Channel::getCurrent()->addSystemMessage(msg, plainText); lastFocusedTimer.restart(); }; // Login to account Command::add("login", [&localNodeUsers, &database, &channels, &loggedIn, &channelMessageMutex, ¤tUsername, ¤tPassword](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 2) { fprintf(stderr, "Expected 2 arguments for command login (username and password), got %u argument(s)\n", args.size()); return; } try { localNodeUsers = database->getStoredNodeUserInfoDecrypted(args[0], args[1]); ChannelSidePanel::removeAllChannels(); for(Channel *channel : channels) { delete channel; } channels.clear(); printf("Loading %u channel(s) for user\n", localNodeUsers.size()); for(auto &localNodeUser : localNodeUsers) { database->loadNode(localNodeUser.first); } printf("Successfully logged into user %s\n", args[0].c_str()); currentUsername = args[0]; currentPassword = args[1]; loggedIn = true; } catch(std::exception &e) { fprintf(stderr, "Failed to login, reason: %s\n", e.what()); } }); // Register account Command::add("register", [&localNodeUsers, &database, &channels, &loggedIn, &channelMessageMutex, ¤tUsername, ¤tPassword](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 2) { fprintf(stderr, "Expected 2 arguments for command register (username and password), got %u argument(s)\n", args.size()); return; } try { database->storeUserWithoutNodes(args[0], args[1]); } catch(odhtdb::SqlExecException &e) { fprintf(stderr, "User with name %s already exists in storage\n", args[0].c_str()); return; } printf("Registered user %s\n", args[0].c_str()); ChannelSidePanel::removeAllChannels(); for(Channel *channel : channels) { delete channel; } channels.clear(); localNodeUsers.clear(); printf("Successfully logged into user %s\n", args[0].c_str()); currentUsername = args[0]; currentPassword = args[1]; loggedIn = true; }); // TODO: Use database->addData to change channel name // Create channel Command::add("cc", [&database, &channels, &channelMessageMutex, &loggedIn, &localNodeUsers, ¤tUsername, ¤tPassword, &lastFocusedTimer, addSystemMessage](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 1) { fprintf(stderr, "Expected 1 argument for command cc (channel name), got %u argument(s)\n", args.size()); return; } if(!loggedIn) { fprintf(stderr, "You are not logged in. Please login before creating a channel\n"); return; } auto createResponse = database->create(); printf("Created channel %s\n", createResponse->getRequestHash()->toString().c_str()); User *newLocalUser = new OnlineLocalUser("NoName", *createResponse->getNodeAdminKeyPair()); odhtdb::DatabaseNode databaseNode(createResponse->getNodeEncryptionKey(), createResponse->getRequestHash()); Channel *channel = new Channel(args[0], databaseNode, newLocalUser, database); ChannelSidePanel::addChannel(channel); channels.push_back(channel); Channel::setCurrent(channel); channel->setName(args[0]); lastFocusedTimer.restart(); localNodeUsers[*createResponse->getRequestHash()] = { createResponse->getNodeEncryptionKey(), createResponse->getNodeAdminKeyPair() }; database->storeNodeInfoForUserEncrypted(databaseNode, currentUsername, currentPassword, *createResponse->getNodeAdminKeyPair()); addSystemMessage("Channel created and stored in database"); }); // Create invite key Command::add("invite", [&channelMessageMutex, &offlineChannel, &database, addSystemMessage](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 0) { fprintf(stderr, "Expected 0 arguments for command invite, got %u argument(s)\n", args.size()); return; } Channel *currentChannel = Channel::getCurrent(); if(currentChannel == &offlineChannel) { addSystemMessage("You need to be in a channel to create an invite key"); return; } // TODO: Verify user has permission to add users before generating invite key, otherwise invite will fail and the users attempting to join will wait in futile auto channelNodeHash = currentChannel->getNodeInfo().getRequestHash(); auto channelEncryptionKey = currentChannel->getNodeInfo().getNodeEncryptionKey(); shared_ptr encryptionKey = make_shared(new u8[odhtdb::ENCRYPTION_KEY_BYTE_SIZE], odhtdb::ENCRYPTION_KEY_BYTE_SIZE); odhtdb::Encryption::generateKey((unsigned char*)encryptionKey->data); string inviteKey = odhtdb::bin2hex((const char*)channelNodeHash->getData(), channelNodeHash->getSize()); inviteKey += '&'; inviteKey += odhtdb::bin2hex((const char*)encryptionKey->data, odhtdb::ENCRYPTION_KEY_BYTE_SIZE); string msg = "You are now listening for users to join the channel using the key: "; msg += inviteKey; addSystemMessage(msg); printf("%s\n", msg.c_str()); sibs::SafeSerializer keySerializer; keySerializer.add((const u8*)channelNodeHash->getData(), channelNodeHash->getSize()); keySerializer.add((const u8*)encryptionKey->data, odhtdb::ENCRYPTION_KEY_BYTE_SIZE); dht::InfoHash key = odhtdb::Database::getInfoHash(keySerializer.getBuffer().data(), keySerializer.getBuffer().size()); database->receiveCustomMessage(key, [&channelMessageMutex, encryptionKey, channelEncryptionKey, currentChannel, &database](const void *data, usize size) { // TODO: User can remove channel @currentChannel before we get here, meaning @currentChannel is deleted and would be invalid; causing the program to crash try { sibs::SafeDeserializer deserializer((const u8*)data, size); u8 userToAddPublicKeyRaw[odhtdb::PUBLIC_KEY_NUM_BYTES]; deserializer.extract(userToAddPublicKeyRaw, odhtdb::PUBLIC_KEY_NUM_BYTES); odhtdb::Signature::PublicKey userToAddPublicKey((const char*)userToAddPublicKeyRaw, odhtdb::PUBLIC_KEY_NUM_BYTES); string unsignedEncryptedMsg = userToAddPublicKey.unsign(odhtdb::DataView((void*)deserializer.getBuffer(), deserializer.getSize())); sibs::SafeDeserializer encryptedDataDeserializer((const u8*)unsignedEncryptedMsg.data(), unsignedEncryptedMsg.size()); u8 nonce[odhtdb::ENCRYPTION_NONCE_BYTE_SIZE]; encryptedDataDeserializer.extract(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE); odhtdb::Decryption decryptedMsg(odhtdb::DataView((void*)encryptedDataDeserializer.getBuffer(), encryptedDataDeserializer.getSize()), odhtdb::DataView(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE), encryptionKey->getView()); // TODO: Create GUI for accepting users into channel instead of accepting ALL users sibs::SafeSerializer encryptedDataSerializer; encryptedDataSerializer.add((const u8*)channelEncryptionKey->data, channelEncryptionKey->size); encryptedDataSerializer.add((const u8*)userToAddPublicKey.getData(), userToAddPublicKey.getSize()); odhtdb::Encryption encryptedChannelKey(odhtdb::DataView((void*)encryptedDataSerializer.getBuffer().data(), encryptedDataSerializer.getBuffer().size()), encryptionKey->getView()); sibs::SafeSerializer serializer; serializer.add((const u8*)encryptedChannelKey.getNonce().data, encryptedChannelKey.getNonce().size); serializer.add((const u8*)encryptedChannelKey.getCipherText().data, encryptedChannelKey.getCipherText().size); const auto &localUserPublicKey = static_cast(currentChannel->getLocalUser())->getPublicKey(); auto localUserGroups = database->getUserGroups(*currentChannel->getNodeInfo().getRequestHash(), localUserPublicKey); if(localUserGroups.empty()) { fprintf(stderr, "No group to add user to...\n"); return sibs::SafeSerializer(); } lock_guard lock(channelMessageMutex); currentChannel->addUser(userToAddPublicKey, localUserGroups[0].getView()); return serializer; } catch(std::exception &e) { fprintf(stderr, "Failed while parsing user data to add to channel, reason: %s\n", e.what()); } return sibs::SafeSerializer(); }); }); // Join channel using invite key Command::add("jc", [&loggedIn, &database, &localNodeUsers, &channelMessageMutex, &waitingToJoin, &waitingToJoinChannels, ¤tUsername, ¤tPassword](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 1) { fprintf(stderr, "Expected 1 argument for command jc (channel join key), got %u argument(s)\n", args.size()); return; } if(args[0].size() != 129) { fprintf(stderr, "Expected join key to be 129 characters, was %u character(s)\n", args[0].size()); return; } if(!loggedIn) { fprintf(stderr, "You are not logged in. Please login before joining a channel\n"); return; } string nodeHashBinRaw = odhtdb::hex2bin(args[0].c_str(), 64); shared_ptr nodeHash = make_shared(); memcpy(nodeHash->getData(), nodeHashBinRaw.data(), nodeHashBinRaw.size()); auto nodeUserIt = localNodeUsers.find(*nodeHash); if(nodeUserIt != localNodeUsers.end()) { fprintf(stderr, "You have already joined the channel %s\n", args[0].c_str()); return; } string encryptionKeyBinRaw = odhtdb::hex2bin(args[0].c_str() + 65, 64); shared_ptr encryptionKey = make_shared(new u8[odhtdb::ENCRYPTION_KEY_BYTE_SIZE], odhtdb::ENCRYPTION_KEY_BYTE_SIZE); memcpy(encryptionKey->data, encryptionKeyBinRaw.data(), encryptionKeyBinRaw.size()); shared_ptr keyPair = make_shared(); sibs::SafeSerializer serializer; serializer.add((const u8*)keyPair->getPublicKey().getData(), keyPair->getPublicKey().getSize()); const char *msg = "please let me join"; odhtdb::Encryption encryptedJoinMsg(odhtdb::DataView((void*)msg, strlen(msg)), encryptionKey->getView()); sibs::SafeSerializer encryptedDataSerializer; encryptedDataSerializer.add((const u8*)encryptedJoinMsg.getNonce().data, encryptedJoinMsg.getNonce().size); encryptedDataSerializer.add((const u8*)encryptedJoinMsg.getCipherText().data, encryptedJoinMsg.getCipherText().size); string signedEncryptedMsg = keyPair->getPrivateKey().sign(odhtdb::DataView(encryptedDataSerializer.getBuffer().data(), encryptedDataSerializer.getBuffer().size())); serializer.add((const u8*)signedEncryptedMsg.data(), signedEncryptedMsg.size()); sibs::SafeSerializer keySerializer; keySerializer.add((const u8*)nodeHash->getData(), nodeHash->getSize()); keySerializer.add((const u8*)encryptionKeyBinRaw.data(), odhtdb::ENCRYPTION_KEY_BYTE_SIZE); dht::InfoHash key = odhtdb::Database::getInfoHash(keySerializer.getBuffer().data(), keySerializer.getBuffer().size()); database->sendCustomMessage(key, move(serializer.getBuffer()), [&database, nodeHash, encryptionKey, &waitingToJoinChannels, &channelMessageMutex, keyPair, ¤tUsername, ¤tPassword, &localNodeUsers](bool gotResponse, const void *data, usize size) { if(!gotResponse) { printf("We didn't get a response from anybody in the channel. Is there nobody that can add us (nobody with the permission required to do so or no online users) or is the node hash invalid?\n"); return false; } try { sibs::SafeDeserializer deserializer((const u8*)data, size); u8 nonce[odhtdb::ENCRYPTION_NONCE_BYTE_SIZE]; deserializer.extract(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE); odhtdb::Decryption decryptedMsg(odhtdb::DataView((void*)deserializer.getBuffer(), deserializer.getSize()), odhtdb::DataView(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE), encryptionKey->getView()); if(decryptedMsg.getDecryptedText().size != odhtdb::ENCRYPTION_KEY_BYTE_SIZE + odhtdb::PUBLIC_KEY_NUM_BYTES) { fprintf(stderr, "Invite response was of unexpected size, maybe it wasn't meant for us?\n"); return true; } odhtdb::DataView channelEncryptionKeyRaw(decryptedMsg.getDecryptedText().data, odhtdb::ENCRYPTION_KEY_BYTE_SIZE); odhtdb::DataView invitedUserPublicKey((u8*)decryptedMsg.getDecryptedText().data + odhtdb::ENCRYPTION_KEY_BYTE_SIZE, odhtdb::PUBLIC_KEY_NUM_BYTES); if(memcmp(keyPair->getPublicKey().getData(), invitedUserPublicKey.data, odhtdb::PUBLIC_KEY_NUM_BYTES) != 0) { fprintf(stderr, "Invite response was not meant for us\n"); return true; } shared_ptr channelEncryptionKey = make_shared(new u8[channelEncryptionKeyRaw.size], channelEncryptionKeyRaw.size); memcpy(channelEncryptionKey->data, channelEncryptionKeyRaw.data, channelEncryptionKeyRaw.size); odhtdb::DatabaseNode databaseNode(channelEncryptionKey, nodeHash); localNodeUsers[*nodeHash] = { channelEncryptionKey, keyPair }; database->storeNodeInfoForUserEncrypted(databaseNode, currentUsername, currentPassword, *keyPair); printf("Got a response from a person in the channel, we might get added...\n"); waitingToJoinChannels.push_back(databaseNode); lock_guard lock(channelMessageMutex); database->seed(databaseNode, odhtdb::DatabaseFetchOrder::NEWEST_FIRST); return false; } catch(std::exception &e) { fprintf(stderr, "Failed while parsing join response for invite link, reason: %s\n", e.what()); } return true; }); waitingToJoin = true; }); // Scale UI Command::add("scale", [](const vector &args) { if(args.size() != 1) { fprintf(stderr, "Expected 1 argument for command scale, got %u argument(s)\n", args.size()); return; } float scaling = stof(args[0]); Settings::setScaling(scaling); printf("UI scaling set to %f\n", scaling); }); Command::add("addbind", [addSystemMessage](const vector &args) { if(args.size() != 2) { string errMsg = "Expected 2 arguments for command addbind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } string key = ":"; key += args[0]; key += ":"; if(key.size() > 255) { // 253 = bind + two colons addSystemMessage("Bind is too long. Max size is 253 bytes"); return; } bool bindAdded = Chatbar::addBind(key, args[1]); if(bindAdded) addSystemMessage("Bind added"); else addSystemMessage("Bind already exists. Remove it first if you want to replace it"); }); Command::add("removebind", [addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command removebind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } string key = ":"; key += args[0]; key += ":"; if(key.size() > 255) { // 253 = bind + two colons addSystemMessage("Bind is too long. Max size is 253 bytes"); return; } bool bindRemoved = Chatbar::removeBind(key); if(bindRemoved) addSystemMessage("Bind removed"); else addSystemMessage("Bind doesn't exist, nothing was removed"); }); Command::add("binds", [addSystemMessage](const vector &args) { if(args.size() != 0) { string errMsg = "Expected 0 arguments for command removebind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } string msg = "Binds:"; auto binds = Chatbar::getBinds(); for(auto &bind : binds) { msg += "\n"; msg += bind.first; msg += " "; msg += bind.second; } addSystemMessage(msg, false); }); // Change nick of current user in current channel Command::add("nick", [&loggedIn, &offlineChannel, addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command nick, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } if(!loggedIn) { addSystemMessage("You need to be logged in to change your nickname"); return; } if(Channel::getCurrent() == &offlineChannel) { addSystemMessage("You need to be in a channel to change your nickname"); return; } if(args[0].size() == 0 || args[0].size() > 255) { addSystemMessage("Invalid nickname. Nickname has to be between 1 and 255 characters"); return; } Channel::getCurrent()->changeNick(args[0]); string msg = "Your nickname was changed to "; msg += args[0]; addSystemMessage(msg); }); // Change avatar of current user in current channel Command::add("avatar", [&loggedIn, &offlineChannel, addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command avatar, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } if(!loggedIn) { addSystemMessage("You need to be logged in to change your avatar"); return; } if(Channel::getCurrent() == &offlineChannel) { addSystemMessage("You need to be in a channel to change your avatar"); return; } if(args[0].size() < 10 || args[0].size() > 512) { addSystemMessage("Invalid avatar url size, expected to be between 10 and 512 bytes"); return; } if(looksLikeUrl(args[0])) { Channel::getCurrent()->setAvatar(args[0]); addSystemMessage("Your avatar has been changed (Note: max avatar size is 1 Mb, if your avatar is larger then it will not be visible)"); } else { addSystemMessage("Avatar url needs to start with either http:// or https:// and include a dot"); } }); // Change name of the current channel Command::add("channelname", [&loggedIn, &offlineChannel, addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command channelname, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } if(!loggedIn) { addSystemMessage("You need to be logged in to change channel name"); return; } if(Channel::getCurrent() == &offlineChannel) { addSystemMessage("You need to be in a channel to change channel name"); return; } if(args[0].size() == 0 || args[0].size() > 32) { addSystemMessage("Channel name has to be between 1 and 32 bytes long"); return; } Channel::getCurrent()->setName(args[0]); string msg = "Channel name has been changed to "; msg += args[0]; addSystemMessage(msg); }); Command::add("clearcache", [&database](const vector &args) { printf("Cleared cache (%d bytes)\n", database->clearCache()); }); string commandsMsg = "Available commands: "; for(const auto &commandIt : Command::getCommands()) { commandsMsg += "\n/"; commandsMsg += commandIt.first; } addSystemMessage(commandsMsg); sf::Clock frameTimer; while (window.isOpen()) { frameTimer.restart(); Channel *currentChannel = Channel::getCurrent(); sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); 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 const int minWidth = 800; if(event.size.width < minWidth) { viewRect.width = minWidth; window.setSize(sf::Vector2u(minWidth, event.size.height)); } */ sf::View view(viewRect); window.setView(view); } else if(event.type == sf::Event::MouseEntered) window.setFramerateLimit(FRAMERATE_FOCUSED); //else if(event.type == sf::Event::MouseLeft) // window.setFramerateLimit(FRAMERATE_NOT_FOCUSED); if(event.type == sf::Event::MouseEntered) focused = true; else if(event.type == sf::Event::MouseLeft) focused = false; if(focused) { ImagePreview::processEvent(event); } if(!ImagePreview::getPreviewContentPtr() && ImagePreview::getTimeSinceLastSeenMs() > 250) { GlobalContextMenu::processEvent(event); currentChannel->processEvent(event, cache); } lastFocusedTimer.restart(); } for(Channel *channel : channels) { channel->update(); } if(lastFocusedTimer.getElapsedTime().asMilliseconds() > 3000) { this_thread::sleep_for(chrono::milliseconds(250)); continue; } window.clear(ColorScheme::getBackgroundColor()); ChannelSidePanel::draw(window); currentChannel->draw(window, cache); UsersSidePanel::draw(window, cache); ChannelTopPanel::draw(window); GlobalContextMenu::draw(window); ImagePreview::draw(window); if(waitingToJoin) { auto windowSize = window.getSize(); sf::RectangleShape shadeRect(sf::Vector2f(windowSize.x, windowSize.y)); shadeRect.setFillColor(sf::Color(0, 0, 0, 200)); window.draw(shadeRect); const sf::Font *FONT = ResourceCache::getFont("fonts/Roboto-Regular.ttf"); const float FONT_SIZE = (double)windowSize.x / 40.0; sf::Text text("Wait until you are added to the channel", *FONT, FONT_SIZE); text.setPosition(floor((float)windowSize.x * 0.5f - text.getLocalBounds().width * 0.5f), floor((float)windowSize.y * 0.5f - FONT->getLineSpacing(FONT_SIZE) * 0.5f)); window.draw(text); } //video.draw(window); window.display(); } for(Channel *channel : channels) { channel->update(); } // We need to wait until our `ping` packet for disconnecting is sent to all channels printf("Shutting down...\n"); this_thread::sleep_for(chrono::seconds(3)); delete database; return 0; }