implement inline mode

This commit is contained in:
Slavasil 2024-10-14 01:31:03 +03:00
parent f049304b1d
commit 910a2e6bd9
4 changed files with 157 additions and 1 deletions

View File

@ -1,11 +1,17 @@
#include "commands.h" #include "commands.h"
#include "common.h"
#include "curl/curl.h" #include "curl/curl.h"
#include "curl/easy.h" #include "curl/easy.h"
#include "td/telegram/td_api.h" #include "td/telegram/td_api.h"
#include "td/tl/TlObject.h"
#include <cstring> #include <cstring>
#include <functional> #include <functional>
#include <random>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <stdexcept>
#include <string>
#include <string_view> #include <string_view>
#include <utility>
#include <vector> #include <vector>
void cmd::handle_regular_message(context *ctx, td_api::message &msg) { 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<uint64_t, pending_inline_query>(pendingQueryId, {query.query_, std::chrono::steady_clock::now() + std::chrono::hours(5)}));
std::vector<td_api::object_ptr<td_api::InputInlineQueryResult>> results;
results.reserve(1);
auto messageButton = td_api::make_object<td_api::inlineKeyboardButton>(
"press to shorten",
static_cast<td_api::object_ptr<td_api::InlineKeyboardButtonType>>(td_api::make_object<td_api::inlineKeyboardButtonTypeCallback>(std::to_string(pendingQueryId)))
);
std::vector<decltype(messageButton)> messageButtonRow;
messageButtonRow.push_back(std::move(messageButton));
std::vector<decltype(messageButtonRow)> messageButtonRows;
messageButtonRows.push_back(std::move(messageButtonRow));
results.push_back(static_cast<td_api::object_ptr<td_api::InputInlineQueryResult>>(td_api::make_object<td_api::inputInlineQueryResultArticle>(
"shorten",
"",
true, // hide_url
"Shorten!",
"...",
"https://slavasil.ru/favicon.ico",
48, 48,
static_cast<td_api::object_ptr<td_api::ReplyMarkup>>(td_api::make_object<td_api::replyMarkupInlineKeyboard>(
std::move(messageButtonRows)
)),
static_cast<td_api::object_ptr<td_api::InputMessageContent>>(td_api::make_object<td_api::inputMessageText>(
td_api::make_object<td_api::formattedText>(
query.query_,
std::move(std::vector<td_api::object_ptr<td_api::textEntity>>())
),
td_api::make_object<td_api::linkPreviewOptions>(true, "", false, false, false),
false // clear_draft
))
)));
ctx->tg->send_query(td_api::make_object<td_api::answerInlineQuery>(
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<td_api::callbackQueryPayloadData>(query.payload_);
ctx->tg->send_query(td_api::make_object<td_api::answerCallbackQuery>(
query.id_,
payload->data_,
false,
"",
0
), [](TgObject obj){
if (obj->get_id() == td_api::error::ID) {
spdlog::error("answerCallbackQuery error: {}", static_cast<td_api::error*>(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<td_api::editInlineMessageText>(
inlineMessageId,
nullptr, // reply_markup
static_cast<td_api::object_ptr<td_api::InputMessageContent>>(td_api::make_object<td_api::inputMessageText>(
td_api::make_object<td_api::formattedText>(
url,
std::move(std::vector<td_api::object_ptr<td_api::textEntity>>())
),
td_api::make_object<td_api::linkPreviewOptions>(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<td_api::editInlineMessageText>(
query.inline_message_id_,
nullptr, // reply_markup
static_cast<td_api::object_ptr<td_api::InputMessageContent>>(td_api::make_object<td_api::inputMessageText>(
td_api::make_object<td_api::formattedText>(
"<сообщение устарело>",
std::move(std::vector<td_api::object_ptr<td_api::textEntity>>())
),
nullptr,
false
))
), {});
}
}
bool cmd::shorten_link(std::string link, context *ctx, std::function<void(std::string)> cb) { bool cmd::shorten_link(std::string link, context *ctx, std::function<void(std::string)> cb) {
spdlog::debug("creating CURL request"); spdlog::debug("creating CURL request");
CURL *req = curl_easy_init(); CURL *req = curl_easy_init();
@ -75,3 +184,9 @@ bool cmd::shorten_link(std::string link, context *ctx, std::function<void(std::s
CURLMcode r = curl_multi_add_handle(ctx->curl, req); CURLMcode r = curl_multi_add_handle(ctx->curl, req);
return r == CURLM_OK; return r == CURLM_OK;
} }
uint64_t cmd::new_pending_query_id() {
static std::mt19937 rng;
static std::uniform_int_distribution<uint64_t> dist(0, 0xFFFFFFFFFFFFFFFFULL);
return dist(rng);
}

View File

@ -6,5 +6,9 @@
namespace cmd { namespace cmd {
void handle_regular_message(context *ctx, td_api::message &msg); 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<void(std::string)> cb); bool shorten_link(std::string link, context *ctx, std::function<void(std::string)> cb);
uint64_t new_pending_query_id();
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "curl/multi.h" #include "curl/multi.h"
#include "uv.h" #include "uv.h"
#include <chrono>
#include <functional> #include <functional>
#include <map> #include <map>
#include <string> #include <string>
@ -55,12 +56,18 @@ struct active_request {
active_request(std::function<void(active_request&)> doneCallback): doneCallback(doneCallback) {} active_request(std::function<void(active_request&)> doneCallback): doneCallback(doneCallback) {}
}; };
struct pending_inline_query {
std::string text;
std::chrono::time_point<std::chrono::steady_clock> ttl;
};
struct context { struct context {
TelegramClient *tg; TelegramClient *tg;
ApiCreds apiCreds; ApiCreds apiCreds;
CURLM *curl; CURLM *curl;
uv_timer_t curlTimer; uv_timer_t curlTimer;
std::map<CURL*, active_request> requests; std::map<CURL*, active_request> requests;
std::map<uint64_t, pending_inline_query> inlineQueries;
}; };
size_t curl_receive_cb(char *ptr, size_t size, size_t nmemb, CURL *ctx); size_t curl_receive_cb(char *ptr, size_t size, size_t nmemb, CURL *ctx);

View File

@ -1,8 +1,11 @@
#include <algorithm>
#include <chrono>
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <sys/select.h>
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
@ -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_socket_cb(uv_poll_t *handle, int status, int events);
void uv_curl_timeout_cb(uv_timer_t *handle); void uv_curl_timeout_cb(uv_timer_t *handle);
void check_curl_multi_info(context *ctx); void check_curl_multi_info(context *ctx);
void gc_pending_queries(uv_timer_t *handle);
int main(int argc, char **argv) { int main(int argc, char **argv) {
CommandLineParams cmdLineParams; CommandLineParams cmdLineParams;
@ -72,6 +76,11 @@ int main(int argc, char **argv) {
uv_idle_init(defaultLoop, &startupHandle); uv_idle_init(defaultLoop, &startupHandle);
uv_idle_start(&startupHandle, async_startup); 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); auto signalHandles = configure_shutdown_signals(defaultLoop, &ctx);
termios termSettings; termios termSettings;
@ -128,7 +137,10 @@ void async_startup(uv_idle_t *h) {
cmd::handle_regular_message(ctx, *upd.message_); cmd::handle_regular_message(ctx, *upd.message_);
}, },
[ctx](td_api::updateNewInlineQuery &upd) { [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){} [](td_api::Object &obj){}
)); ));
@ -307,3 +319,21 @@ size_t curl_receive_cb(char *ptr, size_t size, size_t nmemb, CURL *curl) {
return nmemb; return nmemb;
} }
void gc_pending_queries(uv_timer_t *handle) {
context *ctx = reinterpret_cast<context*>(handle->data);
std::map<uint64_t, pending_inline_query> oldQueries(ctx->inlineQueries);
std::map<uint64_t, pending_inline_query> 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<std::chrono::seconds>(now - i->second.ttl).count());
}
}
ctx->inlineQueries = newQueries;
}