#include "app.h" #include #include #include #include #include "build_config.h" #include "util.h" namespace telegram_tui { namespace { std::string trim_release_tag_prefix(const std::string &value) { if (!value.empty() && (value[0] == 'v' || value[0] == 'V')) { return value.substr(1); } return value; } bool is_truthy_env_value(const std::string &value) { return value == "1" || value == "true" || value == "TRUE" || value == "yes" || value == "YES" || value == "on" || value == "ON"; } std::string shell_quote(const std::string &value) { std::string quoted = "'"; for (char ch : value) { if (ch == '\'') { quoted += "'\\''"; } else { quoted.push_back(ch); } } quoted.push_back('\''); return quoted; } std::string run_command_capture(const std::string &command) { FILE *pipe = popen(command.c_str(), "r"); if (pipe == nullptr) { return {}; } std::string output; std::array buffer{}; while (std::fgets(buffer.data(), static_cast(buffer.size()), pipe) != nullptr) { output += buffer.data(); } if (pclose(pipe) != 0) { return {}; } return output; } std::optional fetch_update_notice() { static constexpr const char *kLatestReleaseApiUrl = "https://git.mshq.dev/api/v1/repos/AxiFisk/shinoa/releases/latest"; if (std::string(TELEGRAM_TUI_BUILD_COMMIT).empty()) { return std::nullopt; } const std::string response = run_command_capture("curl -fsSL " + shell_quote(kLatestReleaseApiUrl) + " 2>/dev/null"); if (response.empty()) { return std::nullopt; } try { const json release = json::parse(response, nullptr, true, true); const std::string latest_tag = trim_release_tag_prefix(safe_string(release, "tag_name")); if (!latest_tag.empty() && latest_tag == TELEGRAM_TUI_PROJECT_VERSION) { return std::nullopt; } const std::string target_commit = safe_string(release, "target_commitish"); if (target_commit.empty()) { return std::nullopt; } const std::string current_commit = TELEGRAM_TUI_BUILD_COMMIT; if (target_commit == current_commit || target_commit.rfind(current_commit, 0) == 0) { return std::nullopt; } return std::string("Update available"); } catch (const json::exception &) { return std::nullopt; } } } // namespace App::App() { const StoredConfig config = load_app_config(); api_id_ = get_env("TELEGRAM_API_ID"); api_hash_ = get_env("TELEGRAM_API_HASH"); if (api_id_.empty()) { api_id_ = TELEGRAM_TUI_BUILD_API_ID; } if (api_hash_.empty()) { api_hash_ = TELEGRAM_TUI_BUILD_API_HASH; } if (api_id_.empty()) { api_id_ = config.api_id; } if (api_hash_.empty()) { api_hash_ = config.api_hash; } auto_reload_chat_history_ = config.auto_reload_chat_history; phone_number_ = get_env("TELEGRAM_PHONE"); use_test_dc_ = is_truthy_env_value(get_env("TELEGRAM_USE_TEST_DC")); const auto root = data_root(); const auto state_root = use_test_dc_ ? (root / "test") : root; database_dir_ = state_root / "tdlib"; files_dir_ = state_root / "files"; std::filesystem::create_directories(database_dir_); std::filesystem::create_directories(files_dir_); if (!api_id_.empty() || !api_hash_.empty()) { persist_config(); } if (use_test_dc_) { status_line_ = "Starting TDLib in test DC mode..."; } start_update_check(); } int App::run() { init_curses(); draw(); while (running_) { bool should_draw = process_updates(); if (advance_attachment_animation()) { should_draw = true; } wint_t ch = 0; const int result = get_wch(&ch); if (result == KEY_CODE_YES) { handle_key(static_cast(ch)); should_draw = true; } else if (result == OK) { if (ch < 128) { handle_key(static_cast(ch)); } else { handle_wide_char(ch); } should_draw = true; } if (should_draw) { draw(); } } shutdown_curses(); return 0; } void App::start_update_check() { update_check_future_ = std::async(std::launch::async, []() { return fetch_update_notice(); }); } bool App::refresh_update_check_result() { if (!update_check_future_.valid()) { return false; } if (update_check_future_.wait_for(std::chrono::seconds(0)) != std::future_status::ready) { return false; } const auto notice = update_check_future_.get(); if (notice == update_notice_) { return false; } update_notice_ = notice.value_or(std::string()); return true; } std::optional App::highlighted_chat_id() const { if (sorted_chat_ids_.empty()) { return std::nullopt; } if (selected_chat_index_ < 0 || selected_chat_index_ >= static_cast(sorted_chat_ids_.size())) { return std::nullopt; } return sorted_chat_ids_[selected_chat_index_]; } std::optional App::open_chat_id() const { if (open_chat_id_ == 0) { return std::nullopt; } return open_chat_id_; } std::optional App::find_message_index(const ChatInfo &chat, std::int64_t message_id) const { for (std::size_t i = 0; i < chat.messages.size(); ++i) { if (chat.messages[i].id == message_id) { return i; } } return std::nullopt; } std::string App::format_message_ref(const ChatInfo &chat, std::int64_t message_id) const { const auto index = find_message_index(chat, message_id); if (index.has_value()) { return "[" + std::to_string(*index + 1) + "]"; } return "#" + std::to_string(message_id); } } // namespace telegram_tui