#include "tgtui.h" #include #include #include #include #include #include #include #include "config.h" #include "crypt.h" namespace pl = std::placeholders; static std::string FormatTime(time_t t) { char timeBuf[20]; std::size_t len = std::strftime(timeBuf, 20, "%Y-%m-%d %H:%M:%S", std::localtime(&t)); return std::string(timeBuf, len); } template static std::vector convertToList(const std::vector& v, std::function f, bool reverse = false) { std::vector out; out.resize(v.size(), nullptr); for(size_t i = 0; i < v.size(); ++i) { const T& t = v.at(i); const std::string value = f(t); char* item = new char[value.size()+1]; std::memcpy(item, value.c_str(), value.size() +1); size_t index = (reverse ? (v.size() - i) -1 : i); out.at(index) = item; } return out; } static void clearVec(std::vector v) { for(char* c : v) { delete[] c; } v.clear(); } TgTUI::TgTUI() : tgclient(std::bind(&TgTUI::initDoneCB, this)), crypt(std::make_unique()) { crypt->load(); } TgTUI::~TgTUI() { crypt->save(); stop(); } void TgTUI::run() { //apply config tgclient.setAuthData( [](){ std::cout << "Auth Code: " << std::flush; std::string code; std::cin >> code; // TODO: take input different return code; }); //register callbacks /* tgclient.registerDraftHandler(std::bind(&UserBot::handleDraft, this, pl::_1, pl::_2)); tgclient.registerActionHandler(std::bind(&UserBot::handleAction, this, pl::_1, pl::_2, pl::_3)); tgclient.registerDeleteHandler(std::bind(&UserBot::handleDelete, this, pl::_1, pl::_2)); tgclient.registerStatusUpdateHandler(std::bind(&UserBot::handleStatus, this, pl::_1)); tgclient.registerEditMessageHandler(std::bind(&UserBot::handleEditMessage, this, pl::_1)); tgclient.registerNewMessageHandler(std::bind(&UserBot::handleMessage, this, pl::_1)); tgclient.registerUserUpdateHandler(std::bind(&Store::logUser, store, pl::_1)); tgclient.registerIndexDoneHandle(std::bind(&UserBot::handleIndexDone, this, pl::_1)); tgclient.registerFileUpdateCallback(std::bind(&UserBot::handleFileUpdate, this, pl::_1)); tgclient.registerChatFiltersCallback(std::bind(&UserBot::handleChatFilters, this, pl::_1)); */ tgclient.registerNewChatCallback(std::bind(&TgTUI::handleNewChat, this, pl::_1)); //run client tgclient.loop(); } void TgTUI::stop() { shouldRun = false; tgclient.stop(); if(tuiThread.joinable()) { tuiThread.join(); } } const std::vector& TgTUI::getChats() { return chats; } void TgTUI::initDoneCB() { shouldRun = true; tuiThread = std::thread(&TgTUI::threadLoop, this); } void TgTUI::threadLoop() { // init ncurses WINDOW* cursesWin = ::initscr(); // ::start_color(); ::cbreak(); // Disable line buffering ::keypad(stdscr, TRUE); // Enable special keys like arrow keys ::noecho(); // Don't print characters to the screen CDKSCREEN* cdkScr = ::initCDKScreen(cursesWin); // build scroll { chatsItemList.reserve(chats.size()); std::vector chatsItemList = convertToList(chats, [](const SlimChat& chat){ std::string name = chat.name; if(name.empty()) { name = std::to_string(chat.chatId); } name.insert(0, " "); return name; }); chatsScroll = newCDKScroll(cdkScr, LEFT, TOP, RIGHT, 0, 40, "Chats:", chatsItemList.data(), chatsItemList.size(), FALSE, A_REVERSE, TRUE, FALSE); clearVec(chatsItemList); } chatScroll = newCDKScroll(cdkScr, 42, TOP, RIGHT, -4, -42, "Chat:", nullptr, 0, FALSE, A_REVERSE, TRUE, FALSE); textEntry = newCDKEntry(cdkScr, 42, BOTTOM, "Text:", "", 0, ' ', EDisplayType::vCHAR, -42, 1, 2048, TRUE, FALSE); while(shouldRun) { activateCDKScroll(chatsScroll, nullptr); if (chatsScroll->exitType == vESCAPE_HIT) { shouldRun = false; } else if (chatsScroll->exitType == vNORMAL) { // build / activate chat view int itemIndex = getCDKScrollCurrentItem(chatsScroll); const SlimChat& chat = chats.at(itemIndex); tgclient.openChat(chat.chatId); tgclient.getLastMessages(chat.chatId, std::bind(&TgTUI::handleChatMessages, this, pl::_1)); activateCDKScroll(chatScroll, nullptr); if(chatScroll->exitType == vNORMAL) { activateCDKEntry(textEntry, nullptr); if(textEntry->exitType == vNORMAL) { char* value = getCDKEntryValue(textEntry); const std::string msg(value); const std::string encMsg = crypt->encryptForChat(chat.chatId, msg); tgclient.sendTextMessage(chat.chatId, encMsg); } } tgclient.closeChat(chat.chatId); } } destroyCDKScroll(chatsScroll); destroyCDKScroll(chatScroll); destroyCDKEntry(textEntry); chats.clear(); tgclient.stop(); // deinit ncurses ::nocbreak(); ::echo(); ::endwin(); } void TgTUI::handleNewChat(objptr chat) { chats.push_back({chat->id_, chat->title_}); } void TgTUI::handleChatMessages(const std::vector>& msgs) { messages = msgs; Log::debug << "new messages: " << msgs.size(); std::vector listEntrys = convertToList>(msgs, [this](const std::shared_ptr& msg) { const char direction = ( msg->isOutgoing ? '>' : '<' ); const std::string timeStr = "[" + FormatTime(msg->sendDate) + "] "; std::string msgText = crypt->decryptFromChat(msg->chat, msg->text); // find and remove first \n std::size_t nPos = msgText.find('\n'); if(nPos != std::string::npos) { msgText.resize(nPos); } if(MessageType::TEXT != msg->type) { msgText = '<' + convertMessageType(msg->type) + "> " + msgText; } return timeStr + direction + " (" + std::to_string(msg->sender) + ") " + msgText; }, true); setCDKScrollItems(chatScroll, listEntrys.data(), listEntrys.size(), FALSE); setCDKScrollPosition(chatScroll, listEntrys.size()-1); clearVec(listEntrys); }