implement real /shorten command
This commit is contained in:
parent
087d70fa0f
commit
f049304b1d
53
commands.cpp
53
commands.cpp
@ -1,6 +1,9 @@
|
|||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
|
#include "curl/curl.h"
|
||||||
|
#include "curl/easy.h"
|
||||||
#include "td/telegram/td_api.h"
|
#include "td/telegram/td_api.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <functional>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -14,6 +17,31 @@ void cmd::handle_regular_message(context *ctx, td_api::message &msg) {
|
|||||||
if (nextSpace != std::string_view::npos)
|
if (nextSpace != std::string_view::npos)
|
||||||
param.remove_suffix(param.size() - nextSpace);
|
param.remove_suffix(param.size() - nextSpace);
|
||||||
spdlog::info("Command /shorten received with parameter '{}'", param);
|
spdlog::info("Command /shorten received with parameter '{}'", param);
|
||||||
|
|
||||||
|
// TODO check URL validity
|
||||||
|
|
||||||
|
bool result = shorten_link(std::string(param), ctx, [tg = ctx->tg, chat_id = msg.chat_id_, thread_id = msg.message_thread_id_](std::string url){
|
||||||
|
tg->send_query(td_api::make_object<td_api::sendMessage>(
|
||||||
|
chat_id, thread_id,
|
||||||
|
nullptr /*reply_to*/, nullptr /*options*/,
|
||||||
|
nullptr, // reply_markup
|
||||||
|
static_cast<td_api::object_ptr<td_api::InputMessageContent>>(td_api::make_object<td_api::inputMessageText>(
|
||||||
|
std::move(td_api::make_object<td_api::formattedText>(url, std::move(std::vector<td_api::object_ptr<td_api::textEntity>>()))),
|
||||||
|
nullptr /*link_preview_options*/, false /*clear_draft*/
|
||||||
|
))
|
||||||
|
), {});
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
ctx->tg->send_query(td_api::make_object<td_api::sendMessage>(
|
||||||
|
msg.chat_id_, msg.message_thread_id_,
|
||||||
|
nullptr /*reply_to*/, nullptr /*options*/,
|
||||||
|
nullptr, // reply_markup
|
||||||
|
static_cast<td_api::object_ptr<td_api::InputMessageContent>>(td_api::make_object<td_api::inputMessageText>(
|
||||||
|
std::move(td_api::make_object<td_api::formattedText>("произошла какая-то ошибка :(", std::move(std::vector<td_api::object_ptr<td_api::textEntity>>()))),
|
||||||
|
nullptr /*link_preview_options*/, false /*clear_draft*/
|
||||||
|
))
|
||||||
|
), {});
|
||||||
|
}
|
||||||
} else if (std::strncmp(text.c_str(), "/shorten", 8) == 0) {
|
} else if (std::strncmp(text.c_str(), "/shorten", 8) == 0) {
|
||||||
std::string textRaw("usage: /shorten <url>");
|
std::string textRaw("usage: /shorten <url>");
|
||||||
std::vector<td_api::object_ptr<td_api::textEntity>> empty;
|
std::vector<td_api::object_ptr<td_api::textEntity>> empty;
|
||||||
@ -22,3 +50,28 @@ void cmd::handle_regular_message(context *ctx, td_api::message &msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cmd::shorten_link(std::string link, context *ctx, std::function<void(std::string)> cb) {
|
||||||
|
spdlog::debug("creating CURL request");
|
||||||
|
CURL *req = curl_easy_init();
|
||||||
|
if (!req) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char *escapedParam = curl_easy_escape(req, link.data(), link.size());
|
||||||
|
std::string url("https://slavasil.ru/create?url=");
|
||||||
|
url += escapedParam;
|
||||||
|
curl_free(escapedParam);
|
||||||
|
curl_easy_setopt(req, CURLOPT_URL, url.c_str());
|
||||||
|
curl_easy_setopt(req, CURLOPT_WRITEFUNCTION, curl_receive_cb);
|
||||||
|
curl_easy_setopt(req, CURLOPT_WRITEDATA, req);
|
||||||
|
curl_easy_setopt(req, CURLOPT_PRIVATE, ctx);
|
||||||
|
curl_easy_setopt(req, CURLOPT_FOLLOWLOCATION, 1);
|
||||||
|
ctx->requests.emplace(req, [cb, req, ctx](active_request &r){
|
||||||
|
std::string shortenedUrl(r.receivedData.data(), r.receivedData.size());
|
||||||
|
spdlog::info("Received data from HTTP server: {}", shortenedUrl);
|
||||||
|
cb(shortenedUrl);
|
||||||
|
ctx->requests.erase(req);
|
||||||
|
});
|
||||||
|
CURLMcode r = curl_multi_add_handle(ctx->curl, req);
|
||||||
|
return r == CURLM_OK;
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "td/telegram/td_api.h"
|
||||||
#include "telegram_client.h"
|
#include "telegram_client.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace cmd {
|
namespace cmd {
|
||||||
void handle_regular_message(context *ctx, td_api::message &msg);
|
void handle_regular_message(context *ctx, td_api::message &msg);
|
||||||
|
bool shorten_link(std::string link, context *ctx, std::function<void(std::string)> cb);
|
||||||
}
|
}
|
16
common.h
16
common.h
@ -1,4 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "curl/multi.h"
|
||||||
|
#include "uv.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
@ -44,7 +48,19 @@ struct CommandLineParams {
|
|||||||
|
|
||||||
struct TelegramClient;
|
struct TelegramClient;
|
||||||
|
|
||||||
|
struct active_request {
|
||||||
|
std::vector<char> receivedData;
|
||||||
|
std::function<void(active_request&)> doneCallback;
|
||||||
|
|
||||||
|
active_request(std::function<void(active_request&)> doneCallback): doneCallback(doneCallback) {}
|
||||||
|
};
|
||||||
|
|
||||||
struct context {
|
struct context {
|
||||||
TelegramClient *tg;
|
TelegramClient *tg;
|
||||||
ApiCreds apiCreds;
|
ApiCreds apiCreds;
|
||||||
|
CURLM *curl;
|
||||||
|
uv_timer_t curlTimer;
|
||||||
|
std::map<CURL*, active_request> requests;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
size_t curl_receive_cb(char *ptr, size_t size, size_t nmemb, CURL *ctx);
|
165
main.cpp
165
main.cpp
@ -1,12 +1,16 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <uv.h>
|
#include <uv.h>
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
|
#include "curl/multi.h"
|
||||||
#include "td/telegram/td_api.h"
|
#include "td/telegram/td_api.h"
|
||||||
#include "td/telegram/td_api.hpp"
|
#include "td/telegram/td_api.hpp"
|
||||||
|
|
||||||
@ -17,11 +21,16 @@ const char *APP_GREETING = "--------------------\n"\
|
|||||||
void async_startup(uv_idle_t *h);
|
void async_startup(uv_idle_t *h);
|
||||||
void configure_blocked_signals();
|
void configure_blocked_signals();
|
||||||
void configure_logging();
|
void configure_logging();
|
||||||
void configure_shutdown_signals(uv_loop_t *loop, context *ctx);
|
std::array<std::unique_ptr<uv_signal_t, void(*)(uv_signal_t*)>, 3> configure_shutdown_signals(uv_loop_t *loop, context *ctx);
|
||||||
CommandLineParams parse_command_line(int argc, char **argv);
|
CommandLineParams parse_command_line(int argc, char **argv);
|
||||||
void print_greeting();
|
void print_greeting();
|
||||||
void print_usage(const char *programName);
|
void print_usage(const char *programName);
|
||||||
void shutdown_signal_handler(uv_signal_t *h, int signum);
|
void shutdown_signal_handler(uv_signal_t *h, int signum);
|
||||||
|
int curl_socket_cb(CURL*, curl_socket_t, int, context*, void*);
|
||||||
|
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);
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
CommandLineParams cmdLineParams;
|
CommandLineParams cmdLineParams;
|
||||||
@ -38,10 +47,22 @@ int main(int argc, char **argv) {
|
|||||||
configure_blocked_signals();
|
configure_blocked_signals();
|
||||||
print_greeting();
|
print_greeting();
|
||||||
|
|
||||||
|
|
||||||
uv_loop_t *defaultLoop = uv_default_loop();
|
uv_loop_t *defaultLoop = uv_default_loop();
|
||||||
context ctx;
|
context ctx;
|
||||||
ctx.apiCreds = cmdLineParams.apiCreds;
|
ctx.apiCreds = cmdLineParams.apiCreds;
|
||||||
|
|
||||||
|
spdlog::info("Initializing CURL");
|
||||||
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||||
|
CURLM *curl = curl_multi_init();
|
||||||
|
ctx.curl = curl;
|
||||||
|
curl_multi_setopt(curl, CURLMOPT_SOCKETFUNCTION, &curl_socket_cb);
|
||||||
|
curl_multi_setopt(curl, CURLMOPT_SOCKETDATA, &ctx);
|
||||||
|
curl_multi_setopt(curl, CURLMOPT_TIMERFUNCTION, &curl_timeout_cb);
|
||||||
|
curl_multi_setopt(curl, CURLMOPT_TIMERDATA, &ctx);
|
||||||
|
uv_timer_init(defaultLoop, &ctx.curlTimer);
|
||||||
|
ctx.curlTimer.data = &ctx;
|
||||||
|
|
||||||
spdlog::info("Creating Telegram client");
|
spdlog::info("Creating Telegram client");
|
||||||
TelegramClient tg(defaultLoop);
|
TelegramClient tg(defaultLoop);
|
||||||
ctx.tg = &tg;
|
ctx.tg = &tg;
|
||||||
@ -51,13 +72,23 @@ 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);
|
||||||
|
|
||||||
configure_shutdown_signals(defaultLoop, &ctx);
|
auto signalHandles = configure_shutdown_signals(defaultLoop, &ctx);
|
||||||
|
|
||||||
|
termios termSettings;
|
||||||
|
tcgetattr(1, &termSettings);
|
||||||
|
termSettings.c_lflag &= ~(ECHO | ICANON);
|
||||||
|
tcsetattr(1, 0, &termSettings);
|
||||||
|
|
||||||
spdlog::info("Entering event loop");
|
spdlog::info("Entering event loop");
|
||||||
uv_run(defaultLoop, UV_RUN_DEFAULT);
|
uv_run(defaultLoop, UV_RUN_DEFAULT);
|
||||||
spdlog::info("Leaving event loop");
|
spdlog::info("Leaving event loop");
|
||||||
|
|
||||||
|
termSettings.c_lflag |= ECHO | ICANON;
|
||||||
|
tcsetattr(1, 0, &termSettings);
|
||||||
|
|
||||||
spdlog::info("Cleaning up");
|
spdlog::info("Cleaning up");
|
||||||
|
curl_multi_cleanup(curl);
|
||||||
|
curl_global_cleanup();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +126,9 @@ void async_startup(uv_idle_t *h) {
|
|||||||
[ctx](td_api::updateNewMessage &upd) {
|
[ctx](td_api::updateNewMessage &upd) {
|
||||||
if (upd.message_->is_outgoing_) return;
|
if (upd.message_->is_outgoing_) return;
|
||||||
cmd::handle_regular_message(ctx, *upd.message_);
|
cmd::handle_regular_message(ctx, *upd.message_);
|
||||||
|
},
|
||||||
|
[ctx](td_api::updateNewInlineQuery &upd) {
|
||||||
|
|
||||||
},
|
},
|
||||||
[](td_api::Object &obj){}
|
[](td_api::Object &obj){}
|
||||||
));
|
));
|
||||||
@ -121,17 +155,21 @@ void configure_blocked_signals() {
|
|||||||
sigprocmask(SIG_BLOCK, &signalSet, nullptr);
|
sigprocmask(SIG_BLOCK, &signalSet, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void register_signal_handle(uv_loop_t *loop, context *ctx, int signum, void(*signal_handler)(uv_signal_t*, int)) {
|
std::unique_ptr<uv_signal_t, void(*)(uv_signal_t*)> register_signal_handle(uv_loop_t *loop, context *ctx, int signum, void(*signal_handler)(uv_signal_t*, int)) {
|
||||||
|
spdlog::debug("register signal handler for {}", signum);
|
||||||
auto deleter = [](uv_signal_t *handle) {
|
auto deleter = [](uv_signal_t *handle) {
|
||||||
uv_close((uv_handle_t*)handle, [](uv_handle_t *h){ delete h; });
|
uv_close((uv_handle_t*)handle, [](uv_handle_t *h){ delete h; });
|
||||||
};
|
};
|
||||||
std::unique_ptr<uv_signal_t, decltype(deleter)> handle(new uv_signal_t, deleter);
|
std::unique_ptr<uv_signal_t, void(*)(uv_signal_t*)> handle(new uv_signal_t, deleter);
|
||||||
uv_signal_init(loop, handle.get());
|
uv_signal_init(loop, handle.get());
|
||||||
uv_signal_start(handle.get(), signal_handler, signum);
|
uv_signal_start(handle.get(), signal_handler, signum);
|
||||||
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void configure_shutdown_signals(uv_loop_t *loop, context *ctx) {
|
std::array<std::unique_ptr<uv_signal_t, void(*)(uv_signal_t*)>, 3> configure_shutdown_signals(uv_loop_t *loop, context *ctx) {
|
||||||
register_signal_handle(loop, ctx, SIGINT, shutdown_signal_handler);
|
return {register_signal_handle(loop, ctx, SIGINT, shutdown_signal_handler),
|
||||||
|
register_signal_handle(loop, ctx, SIGQUIT, shutdown_signal_handler),
|
||||||
|
register_signal_handle(loop, ctx, SIGTERM, shutdown_signal_handler)};
|
||||||
}
|
}
|
||||||
|
|
||||||
void shutdown_signal_handler(uv_signal_t *h, int signum) {
|
void shutdown_signal_handler(uv_signal_t *h, int signum) {
|
||||||
@ -154,3 +192,118 @@ void print_greeting() {
|
|||||||
void print_usage(const char *programName) {
|
void print_usage(const char *programName) {
|
||||||
std::cout << "usage: " << programName << " <api_id> <api_hash> <token>\n";
|
std::cout << "usage: " << programName << " <api_id> <api_hash> <token>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct curl_context {
|
||||||
|
uv_poll_t *pollHandle;
|
||||||
|
curl_socket_t socketFd;
|
||||||
|
context *ctx;
|
||||||
|
|
||||||
|
curl_context(uv_loop_t *loop, context *ctx, int fd) {
|
||||||
|
spdlog::debug("creating new curl context for socket {}", fd);
|
||||||
|
pollHandle = new uv_poll_t;
|
||||||
|
uv_poll_init(loop, pollHandle, fd);
|
||||||
|
pollHandle->data = this;
|
||||||
|
socketFd = fd;
|
||||||
|
this->ctx = ctx;
|
||||||
|
}
|
||||||
|
~curl_context() {
|
||||||
|
spdlog::debug("destroying curl context for socket {}", socketFd);
|
||||||
|
uv_close((uv_handle_t*)pollHandle, [](uv_handle_t *h){ delete h; });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int curl_socket_cb(CURL *curl, curl_socket_t s, int action, context* ctx, void* socketPtr) {
|
||||||
|
(void)curl;
|
||||||
|
spdlog::debug("curl socket callback: {}", action);
|
||||||
|
curl_context *curlCtx;
|
||||||
|
switch (action) {
|
||||||
|
case CURL_POLL_IN:
|
||||||
|
case CURL_POLL_INOUT:
|
||||||
|
case CURL_POLL_OUT: {
|
||||||
|
curlCtx = socketPtr ? (curl_context*)socketPtr : new curl_context(uv_default_loop(), ctx, s);
|
||||||
|
curl_multi_assign(ctx->curl, s, curlCtx);
|
||||||
|
int events = 0;
|
||||||
|
if (action != CURL_POLL_IN) events |= UV_WRITABLE;
|
||||||
|
if (action != CURL_POLL_OUT) events |= UV_READABLE;
|
||||||
|
uv_poll_start(curlCtx->pollHandle, events, uv_socket_cb);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CURL_POLL_REMOVE:
|
||||||
|
if (socketPtr) {
|
||||||
|
uv_poll_stop(((curl_context*)socketPtr)->pollHandle);
|
||||||
|
curl_multi_assign(ctx->curl, s, NULL);
|
||||||
|
delete (curl_context*)socketPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int curl_timeout_cb(CURLM* curl, long timeout, context* ctx) {
|
||||||
|
(void)curl;
|
||||||
|
spdlog::debug("curl timeout change: {} ms", timeout);
|
||||||
|
if (timeout < 0) uv_timer_stop(&ctx->curlTimer);
|
||||||
|
else {
|
||||||
|
if (timeout == 0) timeout = 1;
|
||||||
|
uv_timer_start(&ctx->curlTimer, uv_curl_timeout_cb, timeout, 0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void uv_socket_cb(uv_poll_t *handle, int status, int events) {
|
||||||
|
spdlog::debug("libuv: socket {}", events & UV_READABLE ? (events & UV_WRITABLE ? "readable+writable" : "readable") : "writable");
|
||||||
|
(void)status;
|
||||||
|
auto ctx = reinterpret_cast<curl_context*>(handle->data);
|
||||||
|
int flags = 0;
|
||||||
|
if (events & UV_READABLE) flags |= CURL_CSELECT_IN;
|
||||||
|
if (events & UV_WRITABLE) flags |= CURL_CSELECT_OUT;
|
||||||
|
int runningHandles;
|
||||||
|
curl_multi_socket_action(ctx->ctx->curl, ctx->socketFd, flags, &runningHandles);
|
||||||
|
check_curl_multi_info(ctx->ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uv_curl_timeout_cb(uv_timer_t *handle) {
|
||||||
|
spdlog::debug("libuv: curl timeout ended");
|
||||||
|
auto ctx = reinterpret_cast<context*>(handle->data);
|
||||||
|
if (ctx) {
|
||||||
|
int runningHandles;
|
||||||
|
curl_multi_socket_action(ctx->curl, CURL_SOCKET_TIMEOUT, 0, &runningHandles);
|
||||||
|
check_curl_multi_info(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void check_curl_multi_info(context *ctx) {
|
||||||
|
CURLMsg *message;
|
||||||
|
int pending;
|
||||||
|
while ((message = curl_multi_info_read(ctx->curl, &pending))) {
|
||||||
|
switch (message->msg) {
|
||||||
|
case CURLMSG_DONE: {
|
||||||
|
spdlog::debug("curl transfer done");
|
||||||
|
curl_multi_remove_handle(ctx->curl, message->easy_handle);
|
||||||
|
curl_easy_cleanup(message->easy_handle);
|
||||||
|
try {
|
||||||
|
active_request &req = ctx->requests.at(message->easy_handle);
|
||||||
|
req.doneCallback(req);
|
||||||
|
} catch (std::out_of_range) {}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t curl_receive_cb(char *ptr, size_t size, size_t nmemb, CURL *curl) {
|
||||||
|
spdlog::debug("received {} bytes from server: '{}'", nmemb, std::string(ptr, nmemb));
|
||||||
|
|
||||||
|
context *ctx = nullptr;
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_PRIVATE, &ctx);
|
||||||
|
spdlog::debug("ctx pointer: {}", (size_t)ctx);
|
||||||
|
try {
|
||||||
|
active_request &req = ctx->requests.at(curl);
|
||||||
|
req.receivedData.insert(req.receivedData.end(), ptr, ptr + nmemb);
|
||||||
|
} catch (std::out_of_range) {
|
||||||
|
spdlog::error("No associated request data for handle {}", (size_t)curl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nmemb;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user