219 lines
5.3 KiB
C++
219 lines
5.3 KiB
C++
#include "app.h"
|
|
|
|
#include <array>
|
|
#include <cstdio>
|
|
#include <future>
|
|
|
|
#include <curses.h>
|
|
|
|
#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<char, 4096> buffer{};
|
|
while (std::fgets(buffer.data(), static_cast<int>(buffer.size()), pipe) != nullptr) {
|
|
output += buffer.data();
|
|
}
|
|
if (pclose(pipe) != 0) {
|
|
return {};
|
|
}
|
|
return output;
|
|
}
|
|
|
|
std::optional<std::string> 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<int>(ch));
|
|
should_draw = true;
|
|
} else if (result == OK) {
|
|
if (ch < 128) {
|
|
handle_key(static_cast<int>(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<std::int64_t> App::highlighted_chat_id() const {
|
|
if (sorted_chat_ids_.empty()) {
|
|
return std::nullopt;
|
|
}
|
|
if (selected_chat_index_ < 0 ||
|
|
selected_chat_index_ >= static_cast<int>(sorted_chat_ids_.size())) {
|
|
return std::nullopt;
|
|
}
|
|
return sorted_chat_ids_[selected_chat_index_];
|
|
}
|
|
|
|
std::optional<std::int64_t> App::open_chat_id() const {
|
|
if (open_chat_id_ == 0) {
|
|
return std::nullopt;
|
|
}
|
|
return open_chat_id_;
|
|
}
|
|
|
|
std::optional<std::size_t> 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
|