diff --git a/commands.cpp b/commands.cpp index c833bca..473d7b3 100644 --- a/commands.cpp +++ b/commands.cpp @@ -1,11 +1,17 @@ #include "commands.h" +#include "common.h" #include "curl/curl.h" #include "curl/easy.h" #include "td/telegram/td_api.h" +#include "td/tl/TlObject.h" #include #include +#include #include +#include +#include #include +#include #include void cmd::handle_regular_message(context *ctx, td_api::message &msg) { @@ -51,6 +57,109 @@ void cmd::handle_regular_message(context *ctx, td_api::message &msg) { } } +void cmd::handle_inline_query(context *ctx, td_api::updateNewInlineQuery &query) { + // TODO check URL validity + + uint64_t pendingQueryId = new_pending_query_id(); + ctx->inlineQueries.insert(std::pair(pendingQueryId, {query.query_, std::chrono::steady_clock::now() + std::chrono::hours(5)})); + + std::vector> results; + results.reserve(1); + auto messageButton = td_api::make_object( + "press to shorten", + static_cast>(td_api::make_object(std::to_string(pendingQueryId))) + ); + + std::vector messageButtonRow; + messageButtonRow.push_back(std::move(messageButton)); + + std::vector messageButtonRows; + messageButtonRows.push_back(std::move(messageButtonRow)); + + results.push_back(static_cast>(td_api::make_object( + "shorten", + "", + true, // hide_url + "Shorten!", + "...", + "https://slavasil.ru/favicon.ico", + 48, 48, + static_cast>(td_api::make_object( + std::move(messageButtonRows) + )), + static_cast>(td_api::make_object( + td_api::make_object( + query.query_, + std::move(std::vector>()) + ), + td_api::make_object(true, "", false, false, false), + false // clear_draft + )) + ))); + ctx->tg->send_query(td_api::make_object( + query.id_, + false, // is_personal + nullptr, // button + std::move(results), + 600, // cache_time + "" // next_offset + ), {}); +} + +void cmd::handle_callback_query(context *ctx, td_api::updateNewInlineCallbackQuery &query) { + spdlog::info("callback query"); + if (query.payload_->get_id() != td_api::callbackQueryPayloadData::ID) return; + + auto payload = td::move_tl_object_as(query.payload_); + ctx->tg->send_query(td_api::make_object( + query.id_, + payload->data_, + false, + "", + 0 + ), [](TgObject obj){ + if (obj->get_id() == td_api::error::ID) { + spdlog::error("answerCallbackQuery error: {}", static_cast(obj.get())->message_); + } + }); + + uint64_t queryId = std::stoull(payload->data_); + spdlog::debug("is query in cache? {}", ctx->inlineQueries.contains(queryId)); + try { + pending_inline_query &queryInfo = ctx->inlineQueries.at(queryId); + shorten_link(queryInfo.text, ctx, [ctx, inlineMessageId = query.inline_message_id_](std::string url){ + ctx->tg->send_query(td_api::make_object( + inlineMessageId, + nullptr, // reply_markup + static_cast>(td_api::make_object( + td_api::make_object( + url, + std::move(std::vector>()) + ), + td_api::make_object(true, "", false, false, false), + //nullptr, + false + )) + ), {}); + }); + ctx->inlineQueries.erase(queryId); + } catch (std::out_of_range) { + spdlog::warn("a user tried to use an expired pending query"); + ctx->tg->send_query(td_api::make_object( + query.inline_message_id_, + nullptr, // reply_markup + static_cast>(td_api::make_object( + td_api::make_object( + "<сообщение устарело>", + std::move(std::vector>()) + ), + nullptr, + false + )) + ), {}); + } +} + bool cmd::shorten_link(std::string link, context *ctx, std::function cb) { spdlog::debug("creating CURL request"); CURL *req = curl_easy_init(); @@ -74,4 +183,10 @@ bool cmd::shorten_link(std::string link, context *ctx, std::functioncurl, req); return r == CURLM_OK; +} + +uint64_t cmd::new_pending_query_id() { + static std::mt19937 rng; + static std::uniform_int_distribution dist(0, 0xFFFFFFFFFFFFFFFFULL); + return dist(rng); } \ No newline at end of file diff --git a/commands.h b/commands.h index b929515..9fa2c5f 100644 --- a/commands.h +++ b/commands.h @@ -6,5 +6,9 @@ namespace cmd { void handle_regular_message(context *ctx, td_api::message &msg); + void handle_inline_query(context *ctx, td_api::updateNewInlineQuery &query); + void handle_callback_query(context *ctx, td_api::updateNewInlineCallbackQuery &query); bool shorten_link(std::string link, context *ctx, std::function cb); + + uint64_t new_pending_query_id(); } \ No newline at end of file diff --git a/common.h b/common.h index 823c904..88e9204 100644 --- a/common.h +++ b/common.h @@ -1,6 +1,7 @@ #pragma once #include "curl/multi.h" #include "uv.h" +#include #include #include #include @@ -55,12 +56,18 @@ struct active_request { active_request(std::function doneCallback): doneCallback(doneCallback) {} }; +struct pending_inline_query { + std::string text; + std::chrono::time_point ttl; +}; + struct context { TelegramClient *tg; ApiCreds apiCreds; CURLM *curl; uv_timer_t curlTimer; std::map requests; + std::map inlineQueries; }; size_t curl_receive_cb(char *ptr, size_t size, size_t nmemb, CURL *ctx); \ No newline at end of file diff --git a/main.cpp b/main.cpp index 6513d33..6d3e6ba 100644 --- a/main.cpp +++ b/main.cpp @@ -1,8 +1,11 @@ +#include +#include #include #include #include #include #include +#include #include #include @@ -31,6 +34,7 @@ int curl_timeout_cb(CURLM*, long, context*); void uv_socket_cb(uv_poll_t *handle, int status, int events); void uv_curl_timeout_cb(uv_timer_t *handle); void check_curl_multi_info(context *ctx); +void gc_pending_queries(uv_timer_t *handle); int main(int argc, char **argv) { CommandLineParams cmdLineParams; @@ -72,6 +76,11 @@ int main(int argc, char **argv) { uv_idle_init(defaultLoop, &startupHandle); uv_idle_start(&startupHandle, async_startup); + uv_timer_t pendingQueryGcTimer; + uv_timer_init(defaultLoop, &pendingQueryGcTimer); + pendingQueryGcTimer.data = &ctx; + uv_timer_start(&pendingQueryGcTimer, gc_pending_queries, 60000, 60000); + auto signalHandles = configure_shutdown_signals(defaultLoop, &ctx); termios termSettings; @@ -128,7 +137,10 @@ void async_startup(uv_idle_t *h) { cmd::handle_regular_message(ctx, *upd.message_); }, [ctx](td_api::updateNewInlineQuery &upd) { - + cmd::handle_inline_query(ctx, upd); + }, + [ctx](td_api::updateNewInlineCallbackQuery &upd) { + cmd::handle_callback_query(ctx, upd); }, [](td_api::Object &obj){} )); @@ -306,4 +318,22 @@ size_t curl_receive_cb(char *ptr, size_t size, size_t nmemb, CURL *curl) { } return nmemb; +} + +void gc_pending_queries(uv_timer_t *handle) { + context *ctx = reinterpret_cast(handle->data); + + std::map oldQueries(ctx->inlineQueries); + std::map newQueries; + + auto now = std::chrono::steady_clock::now(); + for (auto i = oldQueries.begin(); i != oldQueries.end(); i++) { + if (i->second.ttl > now) { + newQueries.insert(*i); + } else { + spdlog::debug("deleting a pending query {} that expired {} seconds ago", i->first, std::chrono::duration_cast(now - i->second.ttl).count()); + } + } + + ctx->inlineQueries = newQueries; } \ No newline at end of file diff --git a/tdata/td.binlog b/tdata/td.binlog new file mode 100644 index 0000000..0ae49b9 Binary files /dev/null and b/tdata/td.binlog differ