Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ add_library(tg-cli-lib
src/controllers/GetChatsController.cpp
src/controllers/SendMessageController.cpp
src/controllers/ChatHistoryController.cpp
src/controllers/LabelsController.cpp
src/facade/TgClientFacade.cpp
src/label/LabelsParser.cpp
src/label/LabelsParser.h
)

target_include_directories(tg-cli-lib
Expand Down
20 changes: 4 additions & 16 deletions src/controllers/GetChatsController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ ChatsController::ChatsController(ITgClient& client)
std::vector<ITgClient::Chat> ChatsController::get_chats(int limit) {
clear_error();

if (limit <= 0) {
limit = 10;
}

if (!cached_chats_.empty() && !should_refresh_cache()) {
int return_count = std::min(static_cast<int>(cached_chats_.size()), limit);
return std::vector<ITgClient::Chat>(
auto result = std::vector<ITgClient::Chat>(
cached_chats_.begin(),
cached_chats_.begin() + return_count
);
reverse(result.begin(), result.end());
return result;
}

try {
Expand All @@ -33,21 +31,11 @@ std::vector<ITgClient::Chat> ChatsController::get_chats(int limit) {

std::cout << "[ChatsController] Retrieved " << chats.size()
<< " chats\n";

reverse(chats.begin(), chats.end());
return chats;

} catch (const std::exception& e) {
handle_error(std::string("Failed to get chats: ") + e.what());

if (!cached_chats_.empty()) {
std::cerr << "[ChatsController] Returning cached data due to error\n";
int return_count = std::min(static_cast<int>(cached_chats_.size()), limit);
return std::vector<ITgClient::Chat>(
cached_chats_.begin(),
cached_chats_.begin() + return_count
);
}

return {};
}
}
Expand Down
57 changes: 57 additions & 0 deletions src/controllers/LabelsController.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// Created by Kirtsuha on 13.12.2025.
//

#include "LabelsController.h"

#include "../label/Label.h"
#include "../label/LabelsParser.h"

#include <utility>

LabelsController::LabelsController(std::string filename)
: filename_(std::move(filename)) {
reload();
}

void LabelsController::reload() {
label_to_id_ = LabelsParser::load_labels(filename_);
rebuild_reverse_index();
}

void LabelsController::rebuild_from_chats(const std::vector<ITgClient::Chat>& chats) {
label_to_id_.clear();
id_to_label_.clear();

auto labels = generate_labels(chats.size());
for (size_t i = 0; i < chats.size(); ++i) {
if (chats[i].chatId.empty()) continue;
label_to_id_.insert_or_assign(labels[i], chats[i].chatId);
}

rebuild_reverse_index();
LabelsParser::save_labels(filename_, label_to_id_);
}

std::string LabelsController::resolve_chat_id(const std::string& label_or_id) const {
auto it = label_to_id_.find(label_or_id);
if (it != label_to_id_.end()) {
return it->second;
}
return label_or_id;
}

std::string LabelsController::label_for_chat_id(const std::string& chat_id) const {
auto it = id_to_label_.find(chat_id);
if (it != id_to_label_.end()) {
return it->second;
}
return {};
}

void LabelsController::rebuild_reverse_index() {
id_to_label_.clear();
for (const auto& [label, id] : label_to_id_) {
if (!id.empty()) id_to_label_.insert_or_assign(id, label);
}
}
34 changes: 34 additions & 0 deletions src/controllers/LabelsController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// Created by Kirtsuha on 13.12.2025.
//

#pragma once

#include "../tgClient/ITgClient.hpp"

#include <map>
#include <string>
#include <vector>


class LabelsController {
public:
explicit LabelsController(std::string filename);

void reload();

void rebuild_from_chats(const std::vector<ITgClient::Chat>& chats);

std::string resolve_chat_id(const std::string& label_or_id) const;

std::string label_for_chat_id(const std::string& chat_id) const;

const std::map<std::string, std::string>& labels() const { return label_to_id_; }

private:
std::string filename_;
std::map<std::string, std::string> label_to_id_;
std::map<std::string, std::string> id_to_label_;

void rebuild_reverse_index();
};
64 changes: 48 additions & 16 deletions src/facade/TgClientFacade.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <memory>
#include "TgClientFacade.h"

#include <memory>
#include <iostream>
#include <sstream>
#include <thread>
Expand All @@ -17,6 +17,7 @@ TgClientFacade::TgClientFacade(ITgClient& client) : client_(client) {
chats_controller_ = std::make_unique<ChatsController>(client_);
history_controller_ = std::make_unique<ChatHistoryController>(client_);
message_controller_ = std::make_unique<MessageController>(client_);
label_controller_ = std::make_unique<LabelsController>("labels.txt");
}

std::string TgClientFacade::auth_state_to_string(ITgClient::AuthState state) {
Expand Down Expand Up @@ -45,9 +46,9 @@ void TgClientFacade::print_usage() {
<< " tgcli logout\n"
<< " tgcli chats\n"
<< " tgcli search-chats <query>\n"
<< " tgcli chat-info <chat_id>\n"
<< " tgcli history <chat_id> [limit]\n"
<< " tgcli send <chat_id> <message...>\n"
<< " tgcli chat-info <chat_label_or_id>\n"
<< " tgcli history <chat_label_or_id> [limit]\n"
<< " tgcli send <chat_label_or_id> <message...>\n"
<< std::endl;
}

Expand Down Expand Up @@ -81,13 +82,17 @@ int TgClientFacade::run(int argc, char** argv) {
} else if (command == "logout") {
return handle_logout();
} else if (command == "chats") {
int limit = 20;
int limit;
try {
if (argc < 3) {
limit = std::stoi(argv[2]);
if (limit <= 0) {
limit = 20;
}
}
} catch (...) {
std::cerr << "[tgcli] Invalid limit, using default 20\n";
limit = 20;
}
return handle_get_chats(limit);
} else if (command == "search-chats") {
Expand Down Expand Up @@ -115,8 +120,6 @@ int TgClientFacade::run(int argc, char** argv) {
std::cerr << "[tgcli] Invalid limit, using default 20\n";
}
}
auto x = client_.get_chat_history(argv[2], limit);
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // you saw nothing...

return handle_history(argv[2], limit);

Expand Down Expand Up @@ -212,15 +215,19 @@ int TgClientFacade::handle_logout() {
// CHATS команды
// =====================

int TgClientFacade::handle_get_chats(int limit = 20) {
int TgClientFacade::handle_get_chats(int limit) {
if (!auth_controller_->is_authorized()) {
std::cerr << "[tgcli] Not authorized. Use login-phone/login-code first.\n";
return 1;
}

try {
auto chats = chats_controller_->get_chats(limit);


if (label_controller_) {
label_controller_->rebuild_from_chats(chats);
}

if (chats.empty()) {
std::cout << "[tgcli] No chats found\n";
return 0;
Expand All @@ -231,9 +238,14 @@ int TgClientFacade::handle_get_chats(int limit = 20) {

for (size_t i = 0; i < chats.size(); ++i) {
const auto& chat = chats[i];
const std::string label = label_controller_ ? label_controller_->label_for_chat_id(chat.chatId)
: std::string{};

std::cout << std::setw(3) << (i + 1) << ". "
<< "ID: " << chat.chatId
<< " | Title: " << chat.title << "\n";
<< "Label: " << (label.empty() ? "-" : label)
<< " | Title: " << chat.title
<< " | ID: " << chat.chatId
<< "\n";
}

std::cout << "========================================\n";
Expand Down Expand Up @@ -266,9 +278,16 @@ int TgClientFacade::handle_search_chats(const std::string& query) {

for (size_t i = 0; i < chats.size(); ++i) {
const auto& chat = chats[i];

std::string label;
if (label_controller_) {
label = label_controller_->label_for_chat_id(chat.chatId);
}

std::cout << std::setw(3) << (i + 1) << ". "
<< "ID: " << chat.chatId
<< " | Title: " << chat.title << "\n";
<< "Label: " << (label.empty() ? "-" : label)
<< " | Title: " << chat.title
<< " | ID: " << chat.chatId << "\n";
}

std::cout << "========================================\n";
Expand All @@ -288,14 +307,19 @@ int TgClientFacade::handle_chat_info(const std::string& chat_id) {
}

try {
auto chat = chats_controller_->get_chat_info(chat_id);
const std::string resolved_id = label_controller_ ? label_controller_->resolve_chat_id(chat_id)
: chat_id;
auto chat = chats_controller_->get_chat_info(resolved_id);

if (chat.chatId.empty()) {
std::cerr << "[tgcli] Chat not found: " << chat_id << "\n";
return 1;
}

std::cout << "[tgcli] Chat info:\n";
std::cout << " Label: "
<< (label_controller_ ? label_controller_->label_for_chat_id(chat.chatId) : std::string{"-"})
<< "\n";
std::cout << " ID: " << chat.chatId << "\n";
std::cout << " Title: " << chat.title << "\n";

Expand All @@ -318,7 +342,13 @@ int TgClientFacade::handle_history(const std::string& chat_id, int limit) {
}

try {
auto messages = history_controller_->get_chat_history(chat_id, limit);
const std::string resolved_id = label_controller_ ? label_controller_->resolve_chat_id(chat_id)
: chat_id;
auto messages = history_controller_->get_chat_history(resolved_id, limit);

std::this_thread::sleep_for(std::chrono::milliseconds(800)); // you saw nothing...
messages = client_.get_chat_history(resolved_id, limit);


if (messages.empty()) {
std::cout << "[tgcli] No messages found in chat: " << chat_id << "\n";
Expand Down Expand Up @@ -355,7 +385,9 @@ int TgClientFacade::handle_send(const std::string& chat_id,
}

try {
bool success = message_controller_->send_message(chat_id, message);
const std::string resolved_id = label_controller_ ? label_controller_->resolve_chat_id(chat_id)
: chat_id;
bool success = message_controller_->send_message(resolved_id, message);

if (success) {
std::cout << "[tgcli] Message sent to chat " << chat_id << "\n";
Expand Down
8 changes: 7 additions & 1 deletion src/facade/TgClientFacade.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
#include "../controllers/SendMessageController.h"
#include "../controllers/GetChatsController.h"
#include "../controllers/ChatHistoryController.h"
#include "../controllers/LabelsController.h"
#include "../label/Label.h"
#include "../label/LabelsParser.h"
#include <memory>

class TgClientFacade {
private:
std::unique_ptr<AuthController> auth_controller_;
std::unique_ptr<MessageController> message_controller_;
std::unique_ptr<ChatHistoryController> history_controller_;
std::unique_ptr<ChatsController> chats_controller_;
std::unique_ptr<LabelsController> label_controller_;
ITgClient& client_;

static std::vector<std::string> collect_args(int argc, char** argv);
static void print_usage();


int handle_auth_status();
int handle_login_phone(const std::string& phone);
int handle_login_code(const std::string& code);
Expand All @@ -31,6 +35,8 @@ class TgClientFacade {
int handle_set_target_chat(const std::string& chat_id);
int handle_get_target_history(int limit);

std::map<std::string, std::string> labels;

public:
explicit TgClientFacade(ITgClient& client);
~TgClientFacade() = default;
Expand Down