First version
This commit is contained in:
commit
fb0fcd2669
10 changed files with 540 additions and 0 deletions
13
.clang-format
Normal file
13
.clang-format
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
BasedOnStyle: Google
|
||||||
|
IndentWidth: 2
|
||||||
|
ColumnLimit: 140
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
BreakConstructorInitializers: AfterColon
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
SpaceBeforeParens: Never
|
||||||
|
AllowShortFunctionsOnASingleLine: None
|
||||||
|
AllowShortLambdasOnASingleLine: Empty
|
||||||
|
BinPackArguments: false
|
||||||
|
BinPackParameters: false
|
||||||
|
AlwaysBreakTemplateDeclarations: true
|
1
.clang-tidy
Normal file
1
.clang-tidy
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Checks: '-*,google-*,cppcoreguidelines-*,-cppcoreguidelines-c-copy-assignment-signature,-cppcoreguidelines-special-member-functions,-cppcoreguidelines-avoid-const-or-ref-data-members,modernize-*'
|
35
CMakeLists.txt
Normal file
35
CMakeLists.txt
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
project(yail LANGUAGES CXX)
|
||||||
|
cmake_minimum_required(VERSION 3.28)
|
||||||
|
|
||||||
|
|
||||||
|
set(UTEMPL_URL
|
||||||
|
"https://helicopter.myftp.org/git/sha512sum/utempl"
|
||||||
|
CACHE STRING "utempl repository URL")
|
||||||
|
|
||||||
|
file(
|
||||||
|
DOWNLOAD
|
||||||
|
https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.40.0/CPM.cmake
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake
|
||||||
|
EXPECTED_HASH
|
||||||
|
SHA256=7b354f3a5976c4626c876850c93944e52c83ec59a159ae5de5be7983f0e17a2a
|
||||||
|
)
|
||||||
|
include(${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake)
|
||||||
|
|
||||||
|
CPMAddPackage(
|
||||||
|
NAME utempl
|
||||||
|
URL "${UTEMPL_URL}/archive/refs/heads/main.zip"
|
||||||
|
EXCLUDE_FROM_ALL ON
|
||||||
|
OPTIONS "ENABLE_TESTS OFF" "ENABLE_EXAMPLES OFF"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(yail INTERFACE)
|
||||||
|
|
||||||
|
target_link_libraries(yail INTERFACE utempl::utempl)
|
||||||
|
|
||||||
|
target_include_directories(yail INTERFACE
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||||
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
|
||||||
|
|
||||||
|
add_executable(main main.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(main yail)
|
163
include/yail/core.hpp
Normal file
163
include/yail/core.hpp
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
#pragma once
|
||||||
|
#include <format>
|
||||||
|
#include <utempl/constexpr_string.hpp>
|
||||||
|
#include <utempl/loopholes/core.hpp>
|
||||||
|
#include <utempl/overloaded.hpp>
|
||||||
|
#include <utempl/utils.hpp>
|
||||||
|
#include <yail/utils.hpp>
|
||||||
|
|
||||||
|
namespace yail {
|
||||||
|
|
||||||
|
struct ParsedData {
|
||||||
|
int id;
|
||||||
|
std::string_view other;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace event {
|
||||||
|
|
||||||
|
struct Welcome {
|
||||||
|
std::string_view message;
|
||||||
|
static constexpr auto TryParse(auto& self, std::string_view message) -> std::optional<Welcome> {
|
||||||
|
return self
|
||||||
|
.ParseServerMessage(message) //
|
||||||
|
.and_then([](ParsedData data) { //
|
||||||
|
return data.id == 1 ? std::optional{Welcome{data.other.substr(1)}} : std::nullopt;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Ping {
|
||||||
|
std::string_view data;
|
||||||
|
static constexpr auto TryParse(auto& self, std::string_view message) -> std::optional<Ping> {
|
||||||
|
return message.size() > 4 && message.substr(0, 4) == "PING" ? std::optional{Ping{.data = message.substr(sizeof("PING :") - 1)}}
|
||||||
|
: std::nullopt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace event
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
struct FuncTag {};
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
struct Caster {
|
||||||
|
template <typename T, auto = utempl::loopholes::Injector<FuncTag<F>{}, utempl::kType<T>>{}>
|
||||||
|
consteval operator T(); // NOLINT
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
constexpr Caster<F> kCaster{};
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
using GetEventType = decltype(std::declval<F>()(kCaster<F>, Caster<F>{}), Magic(utempl::loopholes::Getter<FuncTag<F>{}>{}))::Type;
|
||||||
|
|
||||||
|
template <typename Id, typename F>
|
||||||
|
struct Function {
|
||||||
|
F&& f;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Fs = std::tuple<>, auto KeysTuple = utempl::Tuple{}, typename StateTuple = std::tuple<>>
|
||||||
|
struct IrcClient {
|
||||||
|
boost::asio::ip::tcp::socket socket;
|
||||||
|
std::string nick;
|
||||||
|
std::string server;
|
||||||
|
Fs callbacks;
|
||||||
|
StateTuple state;
|
||||||
|
|
||||||
|
template <typename T, utempl::ConstexprString Key, typename... Args>
|
||||||
|
auto AddState(Args&&... args) -> IrcClient<Fs,
|
||||||
|
KeysTuple + utempl::Tuple{Key},
|
||||||
|
decltype(std::tuple_cat(std::move(this->state), std::make_tuple(std::declval<T>())))> {
|
||||||
|
return {.socket = std::move(this->socket),
|
||||||
|
.nick = std::move(this->nick),
|
||||||
|
.server = std::move(this->server),
|
||||||
|
.callbacks = std::move(this->callbacks),
|
||||||
|
.state = std::tuple_cat(std::move(this->state), std::make_tuple(T{std::forward<Args>(args)...}))};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <utempl::ConstexprString Key>
|
||||||
|
auto GetState() -> auto& {
|
||||||
|
constexpr auto strs = utempl::Map(
|
||||||
|
KeysTuple,
|
||||||
|
[](auto& str) {
|
||||||
|
return std::string_view{str};
|
||||||
|
},
|
||||||
|
utempl::kType<std::array<std::string_view, utempl::kTupleSize<decltype(KeysTuple)>>>);
|
||||||
|
constexpr auto i = std::ranges::find(strs, std::string_view{Key}) - strs.begin();
|
||||||
|
return std::get<i>(this->state);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto ParseServerMessage(std::string_view input) -> std::optional<ParsedData> { // NOLINTNEXTLINE
|
||||||
|
return ((input.size() > this->server.size() + 6 + this->nick.size() + 1) ? std::optional{input.substr(this->server.size() + 2)}
|
||||||
|
: std::nullopt)
|
||||||
|
.and_then([&](std::string_view str) {
|
||||||
|
return ToInt(str.substr(0, 3)).transform([&](int i) {
|
||||||
|
return ParsedData{.id = i, .other = str.substr(4 + 1 + this->nick.size())};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Id, typename F>
|
||||||
|
auto With(F&& func) -> IrcClient<decltype(std::tuple_cat(std::move(this->callbacks),
|
||||||
|
std::make_tuple(Function<Id, std::decay_t<F>>{std::forward<F>(func)}))),
|
||||||
|
KeysTuple,
|
||||||
|
StateTuple> {
|
||||||
|
return {.socket = std::move(this->socket),
|
||||||
|
.nick = std::move(this->nick),
|
||||||
|
.server = std::move(this->server),
|
||||||
|
.callbacks = std::tuple_cat(std::move(this->callbacks), std::make_tuple(Function<Id, std::decay_t<F>>{std::forward<F>(func)})),
|
||||||
|
.state = std::move(this->state)};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
auto With(F&& f) -> decltype(With<GetEventType<F>, F>(std::forward<F>(f))) {
|
||||||
|
return With<GetEventType<F>, F>(std::forward<F>(f));
|
||||||
|
};
|
||||||
|
auto SendMessage(std::string_view to, std::string_view content) -> boost::asio::awaitable<void> {
|
||||||
|
const auto request = std::format("PRIVMSG {} :{}", to, content);
|
||||||
|
|
||||||
|
co_await boost::asio::async_write(this->socket, boost::asio::buffer(request), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||||
|
};
|
||||||
|
auto Pong(std::string_view buff) -> boost::asio::awaitable<void> {
|
||||||
|
const auto pong = std::format("PONG {}", buff);
|
||||||
|
|
||||||
|
co_await boost::asio::async_write(this->socket, boost::asio::buffer(pong), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||||
|
};
|
||||||
|
auto Connect(std::string_view realName) -> boost::asio::awaitable<void> {
|
||||||
|
const auto request = std::format("NICK {0}\r\nUSER {0} 0 * :{1}\r\n", this->nick, realName);
|
||||||
|
co_await boost::asio::async_write(socket, boost::asio::buffer(request), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||||
|
};
|
||||||
|
auto Loop() -> boost::asio::awaitable<void> {
|
||||||
|
std::string buff;
|
||||||
|
for(;;) {
|
||||||
|
auto messageStr = co_await yail::ReadLine(this->socket, buff);
|
||||||
|
|
||||||
|
std::println("Readed message: {}", messageStr);
|
||||||
|
co_await std::apply( // NOLINTNEXTLINE
|
||||||
|
[&]<typename... Ids, typename... FFs>(Function<Ids, FFs>&... fs) -> boost::asio::awaitable<void> {
|
||||||
|
auto self = this;
|
||||||
|
auto& str = messageStr;
|
||||||
|
// clang-format off
|
||||||
|
( // NOLINTNEXTLINE
|
||||||
|
co_await [](auto self, auto& messageStr, auto& fs) -> boost::asio::awaitable<void> {
|
||||||
|
if(auto value = Ids::TryParse(*self, messageStr)) {
|
||||||
|
co_await fs.f(*self, *value);
|
||||||
|
}
|
||||||
|
}(self, str, fs),
|
||||||
|
...);
|
||||||
|
// clang-format on
|
||||||
|
},
|
||||||
|
this->callbacks);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
auto Join(std::string_view channel) -> boost::asio::awaitable<std::string> {
|
||||||
|
const auto request = std::format("JOIN {}\r\n", channel);
|
||||||
|
co_await boost::asio::async_write(socket, boost::asio::buffer(request), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||||
|
std::string response;
|
||||||
|
co_await boost::asio::async_read_until(socket, boost::asio::dynamic_buffer(response), "\r\n", boost::asio::use_awaitable);
|
||||||
|
|
||||||
|
co_return response;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace yail
|
27
include/yail/event/users.hpp
Normal file
27
include/yail/event/users.hpp
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
#include <yail/core.hpp>
|
||||||
|
#include <yail/user.hpp>
|
||||||
|
|
||||||
|
namespace yail::event {
|
||||||
|
|
||||||
|
struct ChannelUsers {
|
||||||
|
static constexpr auto kId = 353;
|
||||||
|
std::string_view channel;
|
||||||
|
decltype(ToUsers({})) users;
|
||||||
|
static constexpr auto TryParse(auto& self, std::string_view message) -> std::optional<ChannelUsers> {
|
||||||
|
return self
|
||||||
|
.ParseServerMessage(message) //
|
||||||
|
.and_then([&](ParsedData data) { //
|
||||||
|
return (data.id == kId ? std::optional{data.other.substr(2)} : std::nullopt)
|
||||||
|
.and_then([&](std::string_view data) -> std::optional<ChannelUsers> {
|
||||||
|
auto n = data.find(" ");
|
||||||
|
if(data.size() < n + self.nick.size() + 3) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return ChannelUsers{.channel = data.substr(0, n), .users = ToUsers(data.substr(n + self.nick.size() + 3))};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace yail::event
|
54
include/yail/socks5.hpp
Normal file
54
include/yail/socks5.hpp
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#pragma once
|
||||||
|
#include <ranges>
|
||||||
|
#include <span>
|
||||||
|
#include <yail/utils.hpp>
|
||||||
|
|
||||||
|
namespace yail {
|
||||||
|
|
||||||
|
constexpr std::array kSocks5RequestStart = {std::byte{0x05}, std::byte{0x01}, std::byte{0x00}, std::byte{0x03}};
|
||||||
|
|
||||||
|
constexpr std::size_t kSocks5RequestMaxSize = 257;
|
||||||
|
|
||||||
|
constexpr std::size_t kSocks5ReplyTypeSize = 10;
|
||||||
|
|
||||||
|
constexpr std::array kHandshakeRequest{std::byte{0x05}, std::byte{0x01}, std::byte{0x00}};
|
||||||
|
|
||||||
|
inline auto Socks5ProxyConnect(std::string proxy, std::uint16_t proxyPort, std::string_view address, std::uint16_t port)
|
||||||
|
-> boost::asio::awaitable<boost::asio::ip::tcp::socket> {
|
||||||
|
std::println("Connecting to proxy {}", proxyPort);
|
||||||
|
auto executor = co_await boost::asio::this_coro::executor;
|
||||||
|
boost::asio::ip::tcp::resolver resolver{executor};
|
||||||
|
auto resolved = co_await resolver.async_resolve({std::move(proxy), std::to_string(proxyPort)}, boost::asio::use_awaitable);
|
||||||
|
boost::asio::ip::tcp::socket socket{executor};
|
||||||
|
co_await socket.async_connect(*resolved, boost::asio::use_awaitable);
|
||||||
|
|
||||||
|
std::array<std::byte, 2> handshakeResponse; // NOLINT
|
||||||
|
|
||||||
|
co_await boost::asio::async_write(
|
||||||
|
socket, boost::asio::buffer(kHandshakeRequest), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||||
|
co_await boost::asio::async_read(socket, boost::asio::buffer(handshakeResponse), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||||
|
if(handshakeResponse[0] != std::byte{0x05} || handshakeResponse[1] != std::byte{0x00}) { // NOLINT
|
||||||
|
throw std::exception{};
|
||||||
|
};
|
||||||
|
const auto [connectRequest, size] = [&] {
|
||||||
|
const auto size = static_cast<std::byte>(address.size());
|
||||||
|
const auto htonsPort = std::bit_cast<std::array<std::byte, 2>>(htons(port));
|
||||||
|
auto range = std::array{std::span{kSocks5RequestStart.begin(), kSocks5RequestStart.size()},
|
||||||
|
std::span{&size, 1},
|
||||||
|
std::span{StartLifetimeAsArray<const std::byte>(address.data(), address.size()), address.size()},
|
||||||
|
std::span{htonsPort.data(), 2}} |
|
||||||
|
std::views::join;
|
||||||
|
std::array<std::byte, kSocks5RequestMaxSize> response; // NOLINT
|
||||||
|
return std::pair{std::move(response), std::ranges::copy(range, response.begin()).out - response.begin()};
|
||||||
|
}();
|
||||||
|
co_await boost::asio::async_write(
|
||||||
|
socket, boost::asio::buffer(connectRequest.begin(), size), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||||
|
std::array<std::byte, kSocks5ReplyTypeSize> connectReplyType; // NOLINT
|
||||||
|
co_await boost::asio::async_read(socket, boost::asio::buffer(connectReplyType), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||||
|
if(connectReplyType[1] != std::byte{0x00}) {
|
||||||
|
throw std::exception{};
|
||||||
|
};
|
||||||
|
co_return socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace yail
|
114
include/yail/user.hpp
Normal file
114
include/yail/user.hpp
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
#pragma once
|
||||||
|
#include <format>
|
||||||
|
#include <ranges>
|
||||||
|
#include <string>
|
||||||
|
#include <utempl/overloaded.hpp>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace yail {
|
||||||
|
|
||||||
|
namespace prefix {
|
||||||
|
|
||||||
|
struct None {};
|
||||||
|
|
||||||
|
struct Operator {};
|
||||||
|
|
||||||
|
struct Priveleged {};
|
||||||
|
|
||||||
|
struct Admin {};
|
||||||
|
|
||||||
|
struct HalfOp {};
|
||||||
|
|
||||||
|
struct Owner {};
|
||||||
|
|
||||||
|
} // namespace prefix
|
||||||
|
|
||||||
|
using Prefix = std::variant<prefix::None, prefix::Operator, prefix::Priveleged, prefix::Admin, prefix::HalfOp, prefix::Owner>;
|
||||||
|
|
||||||
|
struct UserView {
|
||||||
|
Prefix prefix;
|
||||||
|
std::string_view nick;
|
||||||
|
static constexpr auto Parse(std::string_view str) -> UserView {
|
||||||
|
if(str.at(0) == '+') {
|
||||||
|
return {.prefix = prefix::Priveleged{}, .nick = str.substr(1)};
|
||||||
|
}
|
||||||
|
if(str.at(0) == '@') {
|
||||||
|
return {.prefix = prefix::Operator{}, .nick = str.substr(1)};
|
||||||
|
}
|
||||||
|
if(str.at(0) == '~') {
|
||||||
|
return {.prefix = prefix::Owner{}, .nick = str.substr(1)};
|
||||||
|
}
|
||||||
|
if(str.at(0) == '%') {
|
||||||
|
return {.prefix = prefix::HalfOp{}, .nick = str.substr(1)};
|
||||||
|
}
|
||||||
|
if(str.at(0) == '&') {
|
||||||
|
return {.prefix = prefix::Admin{}, .nick = str.substr(1)};
|
||||||
|
}
|
||||||
|
return {.prefix = prefix::None{}, .nick = str};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct User {
|
||||||
|
Prefix prefix;
|
||||||
|
std::string nick;
|
||||||
|
constexpr explicit User(UserView view) : prefix(view.prefix), nick(static_cast<std::string>(view.nick)) {};
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
constexpr operator UserView() const {
|
||||||
|
return {.prefix = this->prefix, .nick = this->nick};
|
||||||
|
}
|
||||||
|
constexpr auto operator==(const UserView& other) const -> bool {
|
||||||
|
return this->prefix.index() == other.prefix.index() && this->nick == other.nick;
|
||||||
|
}
|
||||||
|
struct Hash {
|
||||||
|
static constexpr auto operator()(const UserView& user) -> std::size_t {
|
||||||
|
std::size_t hash_prefix = std::hash<std::size_t>{}(user.prefix.index());
|
||||||
|
std::size_t hash_nick = std::hash<std::string_view>{}(user.nick);
|
||||||
|
return hash_prefix ^ (hash_nick << 1);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr auto ToUsers(std::string_view message) {
|
||||||
|
return message | std::views::split(' ') | std::views::transform([](auto range) {
|
||||||
|
return std::string_view{range};
|
||||||
|
}) |
|
||||||
|
std::views::filter([](std::string_view user) {
|
||||||
|
return !user.empty();
|
||||||
|
}) |
|
||||||
|
std::views::transform([](std::string_view user) {
|
||||||
|
return UserView::Parse(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} // namespace yail
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct std::formatter<yail::UserView> : std::formatter<std::string_view> {
|
||||||
|
template <class FmtContext>
|
||||||
|
constexpr auto format(yail::UserView s, FmtContext& ctx) const -> FmtContext::iterator {
|
||||||
|
auto it = ctx.out();
|
||||||
|
std::visit(utempl::Overloaded(
|
||||||
|
[&](yail::prefix::Operator) {
|
||||||
|
*it = '@';
|
||||||
|
++it;
|
||||||
|
},
|
||||||
|
[&](yail::prefix::Priveleged) {
|
||||||
|
*it = '+';
|
||||||
|
++it;
|
||||||
|
},
|
||||||
|
[&](yail::prefix::Owner) {
|
||||||
|
*it = '~';
|
||||||
|
++it;
|
||||||
|
},
|
||||||
|
[&](yail::prefix::HalfOp) {
|
||||||
|
*it = '%';
|
||||||
|
++it;
|
||||||
|
},
|
||||||
|
[&](yail::prefix::Admin) {
|
||||||
|
*it = '&';
|
||||||
|
++it;
|
||||||
|
},
|
||||||
|
[&](yail::prefix::None) {}),
|
||||||
|
s.prefix);
|
||||||
|
return std::ranges::copy(s.nick, it).out;
|
||||||
|
};
|
||||||
|
};
|
84
include/yail/utils.hpp
Normal file
84
include/yail/utils.hpp
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
#pragma once
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <charconv>
|
||||||
|
|
||||||
|
namespace yail {
|
||||||
|
|
||||||
|
#if __has_cpp_attribute(__cpp_lib_start_lifetime_as)
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline auto StartLifetimeAsArray(void* ptr, std::size_t n) -> T* {
|
||||||
|
return std::start_lifetime_as_array<T>(ptr, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline auto StartLifetimeAs(void* ptr) -> T* {
|
||||||
|
return std::start_lifetime_as<T>(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline auto StartLifetimeAsArray(const void* ptr, std::size_t n) -> const T* {
|
||||||
|
return std::start_lifetime_as_array<T>(ptr, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline auto StartLifetimeAs(const void* ptr) -> const T* {
|
||||||
|
return std::start_lifetime_as<T>(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
template <typename T>
|
||||||
|
inline auto StartLifetimeAsArray(void* ptr, std::size_t n) -> T* {
|
||||||
|
return std::launder(reinterpret_cast<T*>(new(ptr) std::byte[n * sizeof(T)])); // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline auto StartLifetimeAs(void* ptr) -> T* {
|
||||||
|
return StartLifetimeAsArray<T>(ptr, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline auto StartLifetimeAsArray(const void* ptr, std::size_t n) -> const T* {
|
||||||
|
return std::launder(reinterpret_cast<const T*>(new(const_cast<void*>(ptr)) std::byte[n * sizeof(T)])); // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline auto StartLifetimeAs(const void* ptr) -> const T* {
|
||||||
|
return StartLifetimeAsArray<T>(ptr, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename T = int>
|
||||||
|
inline auto ToInt(std::string_view input) -> std::optional<T> {
|
||||||
|
T out{};
|
||||||
|
const std::from_chars_result result = std::from_chars(input.data(), input.data() + input.size(), out);
|
||||||
|
return result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range ? std::nullopt : std::optional{out};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ReadLine(auto& socket, std::string& buff) -> boost::asio::awaitable<std::string> {
|
||||||
|
auto it = buff.find("\r\n");
|
||||||
|
return it == std::string::npos ? [](auto& socket, auto& buff) -> boost::asio::awaitable<std::string> { // NOLINT
|
||||||
|
co_await boost::asio::async_read(
|
||||||
|
socket, boost::asio::dynamic_buffer(buff), boost::asio::transfer_at_least(1), boost::asio::use_awaitable);
|
||||||
|
co_return co_await ReadLine(socket, buff);
|
||||||
|
}(socket, buff)
|
||||||
|
: [&](auto it, auto& buff) -> boost::asio::awaitable<std::string> { // NOLINT
|
||||||
|
std::string response;
|
||||||
|
response.reserve(sizeof(std::string) + 1);
|
||||||
|
response = buff.substr(0, it);
|
||||||
|
buff = buff.substr(it + 2);
|
||||||
|
co_return response;
|
||||||
|
}(it, buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Socket>
|
||||||
|
struct BufferedSocket {
|
||||||
|
Socket socket;
|
||||||
|
std::string buff;
|
||||||
|
auto ReadLine() -> boost::asio::awaitable<std::string> {
|
||||||
|
return ::yail::ReadLine(this->socket, this->buff);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace yail
|
3
include/yail/yail.hpp
Normal file
3
include/yail/yail.hpp
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#pragma once
|
||||||
|
#include <yail/event/users.hpp>
|
||||||
|
#include <yail/socks5.hpp>
|
46
main.cpp
Normal file
46
main.cpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <utempl/tuple.hpp>
|
||||||
|
#include <yail/yail.hpp>
|
||||||
|
|
||||||
|
auto Test() -> boost::asio::awaitable<void> {
|
||||||
|
try {
|
||||||
|
auto executor = co_await boost::asio::this_coro::executor; // NOLINTNEXTLINE
|
||||||
|
// auto sock = co_await yail::Socks5ProxyConnect("127.0.0.1", 4447, "irc.ilita.i2p", 6667);
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
auto sock = co_await yail::Socks5ProxyConnect("127.0.0.1", 4447, "irc.ilita.i2p", 6667);
|
||||||
|
auto client =
|
||||||
|
yail::IrcClient<std::tuple<>, utempl::Tuple{}, std::tuple<>>{// clangd bug :(
|
||||||
|
.socket = std::move(sock),
|
||||||
|
.nick = "sha512sum_bot",
|
||||||
|
.server = "irc.ilita.i2p"}
|
||||||
|
.AddState<std::unordered_set<yail::User, yail::User::Hash>, "ruUsers">()
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
.With([](auto& self, yail::event::Ping ping) -> boost::asio::awaitable<void> {
|
||||||
|
co_await self.Pong(ping.data);
|
||||||
|
})
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
.With([](auto& self, yail::event::Welcome message) -> boost::asio::awaitable<void> {
|
||||||
|
std::println("Welcome: {}", message.message);
|
||||||
|
co_await self.Join("#ru");
|
||||||
|
co_await self.SendMessage("#ru", "Hello from yet another irc library");
|
||||||
|
})
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
.With([](auto& self, yail::event::ChannelUsers event) -> boost::asio::awaitable<void> {
|
||||||
|
std::unordered_set<yail::User, yail::User::Hash>& ruUsers = self.template GetState<"ruUsers">();
|
||||||
|
for(yail::UserView view : event.users) {
|
||||||
|
ruUsers.emplace(view);
|
||||||
|
}
|
||||||
|
co_return;
|
||||||
|
});
|
||||||
|
co_await client.Connect("sha512sum_bot");
|
||||||
|
co_await client.Loop();
|
||||||
|
} catch(const std::exception& err) {
|
||||||
|
std::println("Err: {}", err.what());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto main() -> int {
|
||||||
|
boost::asio::io_context context;
|
||||||
|
boost::asio::co_spawn(context, Test(), boost::asio::detached);
|
||||||
|
context.run();
|
||||||
|
};
|
Loading…
Reference in a new issue