mirror of
https://github.com/CHN-beta/nixos.git
synced 2026-01-12 02:09:26 +08:00
Merge branch 'missgram' into production
This commit is contained in:
@@ -19,7 +19,7 @@ namespace biu
|
|||||||
namespace common
|
namespace common
|
||||||
{
|
{
|
||||||
std::size_t hash(auto&&... objs);
|
std::size_t hash(auto&&... objs);
|
||||||
[[gnu::always_inline]] void unused(auto&&...);
|
[[gnu::always_inline]] inline void unused(auto&&...);
|
||||||
[[noreturn]] void block_forever();
|
[[noreturn]] void block_forever();
|
||||||
|
|
||||||
bool is_interactive();
|
bool is_interactive();
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ namespace biu
|
|||||||
protected: const std::chrono::time_point<std::chrono::steady_clock> CreateTime_;
|
protected: const std::chrono::time_point<std::chrono::steady_clock> CreateTime_;
|
||||||
|
|
||||||
// call log<Debug>("create {type} at {address}.");
|
// call log<Debug>("create {type} at {address}.");
|
||||||
protected: [[gnu::always_inline]] ObjectMonitor();
|
protected: [[gnu::always_inline]] inline ObjectMonitor();
|
||||||
|
|
||||||
// call log<Debug>("destroy {type} at {address} after {duration} ms.");
|
// call log<Debug>("destroy {type} at {address} after {duration} ms.");
|
||||||
protected: [[gnu::always_inline]] virtual ~ObjectMonitor();
|
protected: [[gnu::always_inline]] inline virtual ~ObjectMonitor();
|
||||||
};
|
};
|
||||||
template <typename T> friend class ObjectMonitor;
|
template <typename T> friend class ObjectMonitor;
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ namespace biu
|
|||||||
|
|
||||||
// if sizeof...(Param) > 0, call log<Debug>("begin function with {arguments}.");
|
// if sizeof...(Param) > 0, call log<Debug>("begin function with {arguments}.");
|
||||||
// else call log<Debug>("begin function.");
|
// else call log<Debug>("begin function.");
|
||||||
public: template <typename... Param> [[gnu::always_inline]] explicit Guard(Param&&... param);
|
public: template <typename... Param> [[gnu::always_inline]] inline explicit Guard(Param&&... param);
|
||||||
|
|
||||||
// call log<Debug>("end function after {duration} ms.")
|
// call log<Debug>("end function after {duration} ms.")
|
||||||
public: [[gnu::always_inline]] inline virtual ~Guard();
|
public: [[gnu::always_inline]] inline virtual ~Guard();
|
||||||
@@ -74,12 +74,12 @@ namespace biu
|
|||||||
public: [[gnu::always_inline]] inline void operator()() const;
|
public: [[gnu::always_inline]] inline void operator()() const;
|
||||||
|
|
||||||
// call log<Debug>("return {return} after {duration} ms.")
|
// call log<Debug>("return {return} after {duration} ms.")
|
||||||
public: template <typename T> [[gnu::always_inline]] T rtn(T&& value) const;
|
public: template <typename T> [[gnu::always_inline]] inline T rtn(T&& value) const;
|
||||||
|
|
||||||
// print the following message if LoggerConfig_ is set and the level is higher than the level of the
|
// print the following message if LoggerConfig_ is set and the level is higher than the level of the
|
||||||
// LoggerConfig_
|
// LoggerConfig_
|
||||||
// [ {time} {thread} {indent} {filename}:{line} {function_name} ] {message}
|
// [ {time} {thread} {indent} {filename}:{line} {function_name} ] {message}
|
||||||
public: template <Level L> [[gnu::always_inline]] void log(const std::string& message) const;
|
public: template <Level L> [[gnu::always_inline]] inline void log(const std::string& message) const;
|
||||||
public: [[gnu::always_inline]] inline void error(const std::string& message) const;
|
public: [[gnu::always_inline]] inline void error(const std::string& message) const;
|
||||||
public: [[gnu::always_inline]] inline void info(const std::string& message) const;
|
public: [[gnu::always_inline]] inline void info(const std::string& message) const;
|
||||||
public: [[gnu::always_inline]] inline void debug(const std::string& message) const;
|
public: [[gnu::always_inline]] inline void debug(const std::string& message) const;
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ endif()
|
|||||||
find_package(biu REQUIRED)
|
find_package(biu REQUIRED)
|
||||||
find_package(httplib REQUIRED)
|
find_package(httplib REQUIRED)
|
||||||
find_package(sqlgen REQUIRED)
|
find_package(sqlgen REQUIRED)
|
||||||
|
find_package(nlohmann_json REQUIRED)
|
||||||
|
|
||||||
add_executable(missgram src/main.cpp src/db.cpp src/tg.cpp)
|
add_executable(missgram src/main.cpp src/db.cpp src/tg.cpp)
|
||||||
target_include_directories(missgram PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
|
target_include_directories(missgram PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
|
||||||
target_link_libraries(missgram PRIVATE biu::biu httplib::httplib sqlgen::sqlgen)
|
target_link_libraries(missgram PRIVATE biu::biu httplib::httplib sqlgen::sqlgen nlohmann_json::nlohmann_json)
|
||||||
target_compile_features(missgram PRIVATE cxx_std_23)
|
target_compile_features(missgram PRIVATE cxx_std_23)
|
||||||
if(DEFINED MISSGRAM_CONFIG_FILE)
|
if(DEFINED MISSGRAM_CONFIG_FILE)
|
||||||
target_compile_definitions(missgram PRIVATE MISSGRAM_CONFIG_FILE="${MISSGRAM_CONFIG_FILE}")
|
target_compile_definitions(missgram PRIVATE MISSGRAM_CONFIG_FILE="${MISSGRAM_CONFIG_FILE}")
|
||||||
@@ -23,6 +24,15 @@ endif()
|
|||||||
target_compile_definitions(missgram PRIVATE BIU_LOGGER_SOURCE_ROOT="${CMAKE_CURRENT_SOURCE_DIR}")
|
target_compile_definitions(missgram PRIVATE BIU_LOGGER_SOURCE_ROOT="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
install(TARGETS missgram RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install(TARGETS missgram RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
||||||
|
add_executable(tg-test EXCLUDE_FROM_ALL src/tg.cpp src/tg-test.cpp)
|
||||||
|
target_include_directories(tg-test PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
|
||||||
|
target_link_libraries(tg-test PRIVATE biu::biu httplib::httplib nlohmann_json::nlohmann_json)
|
||||||
|
target_compile_features(tg-test PRIVATE cxx_std_23)
|
||||||
|
if(DEFINED MISSGRAM_CONFIG_FILE)
|
||||||
|
target_compile_definitions(tg-test PRIVATE MISSGRAM_CONFIG_FILE="${MISSGRAM_CONFIG_FILE}")
|
||||||
|
endif()
|
||||||
|
target_compile_definitions(tg-test PRIVATE BIU_LOGGER_SOURCE_ROOT="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
get_property(ImportedTargets DIRECTORY "${CMAKE_SOURCE_DIR}" PROPERTY IMPORTED_TARGETS)
|
get_property(ImportedTargets DIRECTORY "${CMAKE_SOURCE_DIR}" PROPERTY IMPORTED_TARGETS)
|
||||||
message("Imported targets: ${ImportedTargets}")
|
message("Imported targets: ${ImportedTargets}")
|
||||||
message("List of compile features: ${CMAKE_CXX_COMPILE_FEATURES}")
|
message("List of compile features: ${CMAKE_CXX_COMPILE_FEATURES}")
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{ lib, stdenv, cmake, pkg-config, biu, configFile ? null, httplib, sqlgen }: stdenv.mkDerivation
|
{ lib, stdenv, cmake, pkg-config, biu, configFile ? null, httplib, sqlgen, nlohmann_json }: stdenv.mkDerivation
|
||||||
{
|
{
|
||||||
name = "missgram";
|
name = "missgram";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
buildInputs = [ biu httplib sqlgen ];
|
buildInputs = [ biu httplib sqlgen nlohmann_json ];
|
||||||
nativeBuildInputs = [ cmake pkg-config ];
|
nativeBuildInputs = [ cmake pkg-config ];
|
||||||
cmakeFlags = lib.optional (configFile != null) [ "-DMISSGRAM_CONFIG_FILE=${configFile}" ];
|
cmakeFlags = lib.optional (configFile != null) [ "-DMISSGRAM_CONFIG_FILE=${configFile}" ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace missgram
|
|||||||
std::int16_t ServerPort;
|
std::int16_t ServerPort;
|
||||||
std::string dbPassword;
|
std::string dbPassword;
|
||||||
} inline config;
|
} inline config;
|
||||||
struct File { std::string url; bool is_photo; bool should_hidden; };
|
struct File { std::string name, url, type; bool isSensitive; };
|
||||||
|
|
||||||
void db_write(std::string misskey_note, std::int32_t telegram_message_id);
|
void db_write(std::string misskey_note, std::int32_t telegram_message_id);
|
||||||
std::optional<std::int32_t> db_read(std::string misskey_note);
|
std::optional<std::int32_t> db_read(std::string misskey_note);
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ int main()
|
|||||||
struct Renote { std::string id; };
|
struct Renote { std::string id; };
|
||||||
std::optional<Renote> renote;
|
std::optional<Renote> renote;
|
||||||
bool localOnly;
|
bool localOnly;
|
||||||
struct File { bool isSensitive; std::string url; std::string type; };
|
|
||||||
std::vector<File> files;
|
std::vector<File> files;
|
||||||
};
|
};
|
||||||
std::optional<Note> note;
|
std::optional<Note> note;
|
||||||
@@ -66,7 +65,7 @@ int main()
|
|||||||
// 否则(引用或普通帖子)
|
// 否则(引用或普通帖子)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
text = *content.body.note->text;
|
text = content.body.note->text.value_or("");
|
||||||
// 如果有引用,则需要查找被引用的帖子是否已经被转发过,若是则直接回复被转发的消息。
|
// 如果有引用,则需要查找被引用的帖子是否已经被转发过,若是则直接回复被转发的消息。
|
||||||
// 如果没有被转发过,则在开头附上链接
|
// 如果没有被转发过,则在开头附上链接
|
||||||
if (content.body.note->renote)
|
if (content.body.note->renote)
|
||||||
@@ -82,21 +81,9 @@ int main()
|
|||||||
text += "\n[在联邦宇宙查看]({}/notes/{})"_f(content.server, content.body.note->id);
|
text += "\n[在联邦宇宙查看]({}/notes/{})"_f(content.server, content.body.note->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 接下来整理要转发的文件
|
|
||||||
auto files = content.body.note->files | ranges::views::transform([](auto&& file) -> File
|
|
||||||
{
|
|
||||||
return File
|
|
||||||
{
|
|
||||||
.url = file.url,
|
|
||||||
.is_photo = file.type.starts_with("image/"),
|
|
||||||
.should_hidden = file.isSensitive
|
|
||||||
};
|
|
||||||
}) | ranges::to_vector;
|
|
||||||
|
|
||||||
log();
|
|
||||||
|
|
||||||
// 异步发送消息
|
// 异步发送消息
|
||||||
std::thread([text, note_id = content.body.note->id, reply_id, files]
|
std::thread([text, note_id = content.body.note->id, reply_id,
|
||||||
|
files = content.body.note->files]
|
||||||
{
|
{
|
||||||
auto message_id = tg_send(text, reply_id, files);
|
auto message_id = tg_send(text, reply_id, files);
|
||||||
if (message_id) db_write(note_id, *message_id);
|
if (message_id) db_write(note_id, *message_id);
|
||||||
|
|||||||
25
packages/missgram/src/tg-test.cpp
Normal file
25
packages/missgram/src/tg-test.cpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# include <missgram.hpp>
|
||||||
|
|
||||||
|
# ifndef MISSGRAM_CONFIG_FILE
|
||||||
|
# define MISSGRAM_CONFIG_FILE "./config.yaml"
|
||||||
|
# endif
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
using namespace biu::literals;
|
||||||
|
using namespace missgram;
|
||||||
|
biu::Logger::Guard log;
|
||||||
|
|
||||||
|
config = YAML::LoadFile(MISSGRAM_CONFIG_FILE).as<Config>();
|
||||||
|
// tg_send("aaaa", std::nullopt, {});
|
||||||
|
// tg_send("aaaa", std::nullopt, {{"IMG20241013173523.jpg", "https://xn--s8w913fdga.chn.moe/files/3dd41113-4df5-4f34-a825-e4137d146172", "image/jpeg", false}});
|
||||||
|
tg_send("aaaa", std::nullopt,
|
||||||
|
{
|
||||||
|
{"2026-01-07 22-02-22 1.png", "https://xn--s8w913fdga.chn.moe/files/a23d13ea-de37-4907-9d54-66417d7e0e36", "image/png", false}
|
||||||
|
});
|
||||||
|
// tg_send("aaaa", std::nullopt,
|
||||||
|
// {
|
||||||
|
// {"IMG20241013173523.jpg", "https://xn--s8w913fdga.chn.moe/files/// 3dd41113-4df5-4f34-a825-e4137d146172", "image/jpeg", true},
|
||||||
|
// {"2026-01-07 22-02-22 1.png", "https://xn--s8w913fdga.chn.moe/files/// a23d13ea-de37-4907-9d54-66417d7e0e36", "image/png", false}
|
||||||
|
// });
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# include <missgram.hpp>
|
# include <missgram.hpp>
|
||||||
# include <tgbot/tgbot.h>
|
# include <httplib.h>
|
||||||
|
# include <nlohmann/json.hpp>
|
||||||
|
|
||||||
std::optional<std::int32_t> missgram::tg_send
|
std::optional<std::int32_t> missgram::tg_send
|
||||||
(std::string text, std::optional<std::int32_t> replyId, std::vector<File> files)
|
(std::string text, std::optional<std::int32_t> replyId, std::vector<File> files)
|
||||||
@@ -7,84 +8,121 @@ std::optional<std::int32_t> missgram::tg_send
|
|||||||
using namespace biu::literals;
|
using namespace biu::literals;
|
||||||
biu::Logger::Guard log;
|
biu::Logger::Guard log;
|
||||||
|
|
||||||
// 整理要发送的信息
|
|
||||||
TgBot::Bot bot(config.TelegramBotToken);
|
|
||||||
std::shared_ptr<TgBot::ReplyParameters> reply;
|
|
||||||
if (replyId) reply = std::make_shared<TgBot::ReplyParameters>(*replyId, config.TelegramChatId);
|
|
||||||
auto attachs = files
|
|
||||||
| ranges::views::transform([&](auto&& file) -> TgBot::InputMedia::Ptr
|
|
||||||
{
|
|
||||||
if (file.is_photo)
|
|
||||||
{
|
|
||||||
auto pic = std::make_shared<TgBot::InputMediaPhoto>();
|
|
||||||
pic->media = file.url;
|
|
||||||
pic->hasSpoiler = file.should_hidden;
|
|
||||||
return pic;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto doc = std::make_shared<TgBot::InputMediaDocument>();
|
|
||||||
doc->media = file.url;
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
| ranges::to_vector;
|
|
||||||
|
|
||||||
// 多次尝试运行函数,直到成功或达到最大尝试次数(5次)
|
// 多次尝试运行函数,直到成功或达到最大尝试次数(5次)
|
||||||
auto try_run = [&](auto&& func) -> std::optional<std::int32_t>
|
auto try_run = [&](auto&& func) -> std::optional<decltype(func())>
|
||||||
{
|
{
|
||||||
auto retry_delay = 1s;
|
auto retry_delay = 1s;
|
||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
while (attempts < 5)
|
while (attempts < 5)
|
||||||
{
|
{
|
||||||
TgBot::Message::Ptr message;
|
std::optional<decltype(func())> result;
|
||||||
biu::Logger::try_exec([&] { message = func(); });
|
biu::Logger::try_exec([&] { result = func(); });
|
||||||
if (message) return message->messageId;
|
if (result) return result;
|
||||||
std::this_thread::sleep_for(retry_delay);
|
std::this_thread::sleep_for(retry_delay);
|
||||||
retry_delay *= 2;
|
retry_delay *= 2;
|
||||||
attempts++;
|
attempts++;
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果没有附件,使用 sendMessage 发送文本消息
|
// 下载一个 https 资源到内存
|
||||||
if (attachs.empty()) return try_run([&] { return bot.getApi().sendMessage
|
auto download = [&](const std::string& url) -> std::string
|
||||||
(
|
|
||||||
config.TelegramChatId, text, nullptr, reply, nullptr,
|
|
||||||
"MarkdownV2"
|
|
||||||
);});
|
|
||||||
// 如果只有一个附件并且是图片,使用 sendPhoto 发送
|
|
||||||
else if (attachs.size() == 1 && files[0].is_photo) return try_run([&]
|
|
||||||
{
|
{
|
||||||
return bot.getApi().sendPhoto
|
std::regex https_regex(R"(https://([^/]+)(/.+))");
|
||||||
(
|
std::smatch match;
|
||||||
config.TelegramChatId, files[0].url, text, reply,
|
if (!std::regex_match(url, match, https_regex))
|
||||||
nullptr, "MarkdownV2", false, {}, 0, false, files[0].should_hidden
|
throw std::runtime_error("Only https URLs are supported");
|
||||||
);
|
httplib::SSLClient cli(match[1].str());
|
||||||
|
auto res = cli.Get(match[2].str());
|
||||||
|
if (res && res->status == 200) return res->body;
|
||||||
|
else throw std::runtime_error("Failed to download file from " + url);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下载要发送的文件
|
||||||
|
std::vector<std::string> file_contents;
|
||||||
|
for (const auto& file : files)
|
||||||
|
{
|
||||||
|
auto content = try_run([&] { return download(file.url); });
|
||||||
|
if (!content) throw std::runtime_error("Failed to download file from " + file.url);
|
||||||
|
file_contents.push_back(std::move(*content));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备要发送的请求
|
||||||
|
httplib::UploadFormDataItems items;
|
||||||
|
std::string method;
|
||||||
|
if (files.empty())
|
||||||
|
{
|
||||||
|
method = "sendMessage";
|
||||||
|
items.push_back({"text", text});
|
||||||
|
items.push_back({"parse_mode", "MarkdownV2"});
|
||||||
|
items.push_back({"link_preview_options",
|
||||||
|
[]{ nlohmann::json j; j["is_disabled"] = true; return j.dump(); }()});
|
||||||
|
}
|
||||||
|
else if (files.size() == 1)
|
||||||
|
{
|
||||||
|
auto is_photo = files[0].type.starts_with("image/");
|
||||||
|
method = is_photo ? "sendPhoto" : "sendDocument";
|
||||||
|
items.push_back
|
||||||
|
({
|
||||||
|
is_photo ? "photo" : "document",
|
||||||
|
file_contents[0], files[0].name, files[0].type
|
||||||
});
|
});
|
||||||
// 如果有多个附件,使用 sendMediaGroup 分两条消息发送,返回第一条的 id
|
items.push_back({"caption", text});
|
||||||
|
items.push_back({"parse_mode", "MarkdownV2"});
|
||||||
|
if (is_photo && files[0].isSensitive) items.push_back({"has_spoiler", "True"});
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto message = try_run([&] { return bot.getApi().sendMessage
|
method = "sendMediaGroup";
|
||||||
(
|
auto all_photo = ranges::all_of(files,
|
||||||
config.TelegramChatId, text, nullptr, reply, nullptr,
|
[](auto&& file) { return file.type.starts_with("image/"); });
|
||||||
"MarkdownV2"
|
nlohmann::json media_group = files | ranges::views::enumerate | ranges::views::transform([&](auto&& file)
|
||||||
);});
|
|
||||||
if (message)
|
|
||||||
{
|
{
|
||||||
auto message2 = try_run([&] -> TgBot::Message::Ptr
|
nlohmann::json params;
|
||||||
|
if (all_photo)
|
||||||
{
|
{
|
||||||
auto msg = bot.getApi().sendMediaGroup
|
params["type"] = "photo";
|
||||||
(
|
if (file.second.isSensitive) params["has_spoiler"] = true;
|
||||||
config.TelegramChatId, attachs, false,
|
|
||||||
std::make_shared<TgBot::ReplyParameters>(*message, config.TelegramChatId)
|
|
||||||
);
|
|
||||||
if (msg.empty() || !ranges::all_of(msg, [](auto&& m) { return bool(m); }))
|
|
||||||
return nullptr;
|
|
||||||
else return msg[0];
|
|
||||||
});
|
|
||||||
if (!message2) return {};
|
|
||||||
}
|
}
|
||||||
return message;
|
else params["type"] = "document";
|
||||||
|
params["media"] = "attach://media-{}"_f(file.first);
|
||||||
|
log.debug("Prepared media {}: {}"_f(file.first, params.dump()));
|
||||||
|
if (file.first == 0) { params["caption"] = text; params["parse_mode"] = "MarkdownV2"; }
|
||||||
|
return params;
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
items.push_back({"media", media_group.dump()});
|
||||||
|
for (int i = 0; i < files.size(); i++) items.push_back
|
||||||
|
({"media-{}"_f(i), file_contents[i], files[i].name, files[i].type});
|
||||||
|
}
|
||||||
|
items.push_back({"chat_id", std::to_string(config.TelegramChatId)});
|
||||||
|
if (replyId) items.push_back({"reply_parameters", [&]
|
||||||
|
{
|
||||||
|
nlohmann::json j;
|
||||||
|
j["message_id"] = *replyId;
|
||||||
|
j["chat_id"] = config.TelegramChatId;
|
||||||
|
return j.dump();
|
||||||
|
}()});
|
||||||
|
|
||||||
|
items.push_back({"disable_notification", "True"});
|
||||||
|
httplib::Client cli("https://api.telegram.org");
|
||||||
|
auto result = cli.Post("/bot{}/{}"_f(config.TelegramBotToken, method), items);
|
||||||
|
log.debug("{} {} {}"_f(result->status, result->body, result->headers));
|
||||||
|
if (result && result->status == 200)
|
||||||
|
{
|
||||||
|
auto json = nlohmann::json::parse(result->body);
|
||||||
|
// 测试 js["result"]["message_id"] 是否存在且为整数
|
||||||
|
if (json.contains("result") && json["result"].contains("message_id")
|
||||||
|
&& json["result"]["message_id"].is_number_integer())
|
||||||
|
return json["result"]["message_id"].get<std::int32_t>();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.error("Telegram API error: {}"_f(json.dump()));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.error("HTTP error: {}"_f(result ? std::to_string(result->status) : "No response"));
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user