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 "curl/curl.h"
|
||||
#include "curl/easy.h"
|
||||
#include "td/telegram/td_api.h"
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@ -14,6 +17,31 @@ void cmd::handle_regular_message(context *ctx, td_api::message &msg) {
|
||||
if (nextSpace != std::string_view::npos)
|
||||
param.remove_suffix(param.size() - nextSpace);
|
||||
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) {
|
||||
std::string textRaw("usage: /shorten <url>");
|
||||
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
|
||||
#include "common.h"
|
||||
#include "td/telegram/td_api.h"
|
||||
#include "telegram_client.h"
|
||||
#include <string>
|
||||
|
||||
namespace cmd {
|
||||
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
|
||||
#include "curl/multi.h"
|
||||
#include "uv.h"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace detail {
|
||||
@ -44,7 +48,19 @@ struct CommandLineParams {
|
||||
|
||||
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 {
|
||||
TelegramClient *tg;
|
||||
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 <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <uv.h>
|
||||
#include "common.h"
|
||||
#include "commands.h"
|
||||
#include "curl/multi.h"
|
||||
#include "td/telegram/td_api.h"
|
||||
#include "td/telegram/td_api.hpp"
|
||||
|
||||
@ -17,11 +21,16 @@ const char *APP_GREETING = "--------------------\n"\
|
||||
void async_startup(uv_idle_t *h);
|
||||
void configure_blocked_signals();
|
||||
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);
|
||||
void print_greeting();
|
||||
void print_usage(const char *programName);
|
||||
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) {
|
||||
CommandLineParams cmdLineParams;
|
||||
@ -38,10 +47,22 @@ int main(int argc, char **argv) {
|
||||
configure_blocked_signals();
|
||||
print_greeting();
|
||||
|
||||
|
||||
uv_loop_t *defaultLoop = uv_default_loop();
|
||||
context ctx;
|
||||
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");
|
||||
TelegramClient tg(defaultLoop);
|
||||
ctx.tg = &tg;
|
||||
@ -51,13 +72,23 @@ int main(int argc, char **argv) {
|
||||
uv_idle_init(defaultLoop, &startupHandle);
|
||||
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");
|
||||
uv_run(defaultLoop, UV_RUN_DEFAULT);
|
||||
spdlog::info("Leaving event loop");
|
||||
|
||||
termSettings.c_lflag |= ECHO | ICANON;
|
||||
tcsetattr(1, 0, &termSettings);
|
||||
|
||||
spdlog::info("Cleaning up");
|
||||
curl_multi_cleanup(curl);
|
||||
curl_global_cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -95,6 +126,9 @@ void async_startup(uv_idle_t *h) {
|
||||
[ctx](td_api::updateNewMessage &upd) {
|
||||
if (upd.message_->is_outgoing_) return;
|
||||
cmd::handle_regular_message(ctx, *upd.message_);
|
||||
},
|
||||
[ctx](td_api::updateNewInlineQuery &upd) {
|
||||
|
||||
},
|
||||
[](td_api::Object &obj){}
|
||||
));
|
||||
@ -121,17 +155,21 @@ void configure_blocked_signals() {
|
||||
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) {
|
||||
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_start(handle.get(), signal_handler, signum);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void configure_shutdown_signals(uv_loop_t *loop, context *ctx) {
|
||||
register_signal_handle(loop, ctx, SIGINT, shutdown_signal_handler);
|
||||
std::array<std::unique_ptr<uv_signal_t, void(*)(uv_signal_t*)>, 3> configure_shutdown_signals(uv_loop_t *loop, context *ctx) {
|
||||
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) {
|
||||
@ -154,3 +192,118 @@ void print_greeting() {
|
||||
void print_usage(const char *programName) {
|
||||
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