193 lines
5.2 KiB
C++
193 lines
5.2 KiB
C++
#include "tgtui.h"
|
|
|
|
#include <ctime>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
#include <Log.h>
|
|
|
|
#include <curses.h>
|
|
#include <cdk/cdk.h>
|
|
|
|
#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<typename T>
|
|
static std::vector<char*> convertToList(const std::vector<T>& v, std::function<std::string(const T&)> f, bool reverse = false) {
|
|
std::vector<char*> 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<char*> v) {
|
|
for(char* c : v) {
|
|
delete[] c;
|
|
}
|
|
v.clear();
|
|
}
|
|
|
|
TgTUI::TgTUI() : tgclient(std::bind(&TgTUI::initDoneCB, this)), crypt(std::make_unique<Crypt>()) {}
|
|
|
|
TgTUI::~TgTUI() {
|
|
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<SlimChat>& 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<char*> chatsItemList = convertToList<SlimChat>(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);
|
|
chatScroll = newCDKScroll(cdkScr, 41, TOP, RIGHT, 0, -41, "Chat:", nullptr, 0, FALSE, A_REVERSE, TRUE, FALSE);
|
|
clearVec(chatsItemList);
|
|
}
|
|
|
|
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);
|
|
|
|
tgclient.closeChat(chat.chatId);
|
|
}
|
|
}
|
|
|
|
destroyCDKScroll(chatsScroll);
|
|
destroyCDKScroll(chatScroll);
|
|
|
|
chats.clear();
|
|
|
|
tgclient.stop();
|
|
|
|
// deinit ncurses
|
|
::nocbreak();
|
|
::echo();
|
|
::endwin();
|
|
}
|
|
|
|
void TgTUI::handleNewChat(objptr<td_api::chat> chat) {
|
|
chats.push_back({chat->id_, chat->title_});
|
|
}
|
|
|
|
void TgTUI::handleChatMessages(const std::vector<std::shared_ptr<Message>>& msgs) {
|
|
messages = msgs;
|
|
|
|
Log::debug << "new messages: " << msgs.size();
|
|
|
|
std::vector<char*> listEntrys = convertToList<std::shared_ptr<Message>>(msgs, [this](const std::shared_ptr<Message>& 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);
|
|
}
|