Skip to content
Snippets Groups Projects
Commit bf0e55ed authored by Konstantinos Sideris's avatar Konstantinos Sideris
Browse files

Add another small example

parent cb1db869
No related branches found
No related tags found
No related merge requests found
......@@ -146,7 +146,9 @@ endif()
if (BUILD_LIB_EXAMPLES)
add_executable(room_feed examples/room_feed.cpp)
add_executable(simple_bot examples/simple_bot.cpp)
target_link_libraries(room_feed matrix_client ${MATRIX_STRUCTS_LIBRARY})
target_link_libraries(simple_bot matrix_client ${MATRIX_STRUCTS_LIBRARY})
endif()
if (BUILD_LIB_TESTS)
......
#include <boost/algorithm/string/predicate.hpp>
#include <boost/beast.hpp>
#include <iostream>
#include <json.hpp>
#include <unistd.h>
#include <variant.hpp>
#include <mtx.hpp>
#include <mtx/identifiers.hpp>
#include "mtxclient/http/client.hpp"
#include "mtxclient/http/errors.hpp"
//
// Simple example bot that will accept any invite.
//
using namespace std;
using namespace mtx::client;
using namespace mtx::http;
using namespace mtx::events;
using namespace mtx::identifiers;
using TimelineEvent = mtx::events::collections::TimelineEvents;
namespace {
std::shared_ptr<Client> client = nullptr;
}
void
print_errors(RequestErr err)
{
if (err->status_code != boost::beast::http::status::unknown)
cout << err->status_code << "\n";
if (!err->matrix_error.error.empty())
cout << err->matrix_error.error << "\n";
if (err->error_code)
cout << err->error_code.message() << "\n";
}
// Check if the given event has a textual representation.
bool
is_room_message(const TimelineEvent &event)
{
return mpark::holds_alternative<mtx::events::RoomEvent<msg::Audio>>(event) ||
mpark::holds_alternative<mtx::events::RoomEvent<msg::Emote>>(event) ||
mpark::holds_alternative<mtx::events::RoomEvent<msg::File>>(event) ||
mpark::holds_alternative<mtx::events::RoomEvent<msg::Image>>(event) ||
mpark::holds_alternative<mtx::events::RoomEvent<msg::Notice>>(event) ||
mpark::holds_alternative<mtx::events::RoomEvent<msg::Text>>(event) ||
mpark::holds_alternative<mtx::events::RoomEvent<msg::Video>>(event);
}
// Retrieves the fallback body value from the event.
std::string
get_body(const TimelineEvent &event)
{
if (mpark::holds_alternative<RoomEvent<msg::Audio>>(event))
return mpark::get<RoomEvent<msg::Audio>>(event).content.body;
else if (mpark::holds_alternative<RoomEvent<msg::Emote>>(event))
return mpark::get<RoomEvent<msg::Emote>>(event).content.body;
else if (mpark::holds_alternative<RoomEvent<msg::File>>(event))
return mpark::get<RoomEvent<msg::File>>(event).content.body;
else if (mpark::holds_alternative<RoomEvent<msg::Image>>(event))
return mpark::get<RoomEvent<msg::Image>>(event).content.body;
else if (mpark::holds_alternative<RoomEvent<msg::Notice>>(event))
return mpark::get<RoomEvent<msg::Notice>>(event).content.body;
else if (mpark::holds_alternative<RoomEvent<msg::Text>>(event))
return mpark::get<RoomEvent<msg::Text>>(event).content.body;
else if (mpark::holds_alternative<RoomEvent<msg::Video>>(event))
return mpark::get<RoomEvent<msg::Video>>(event).content.body;
return "";
}
// Retrieves the sender of the event.
std::string
get_sender(const TimelineEvent &event)
{
return mpark::visit([](auto e) { return e.sender; }, event);
}
void
parse_messages(const mtx::responses::Sync &res, bool parse_repeat_cmd = false)
{
for (const auto room : res.rooms.invite) {
auto room_id = parse<Room>(room.first);
printf("joining room %s\n", room_id.to_string().c_str());
client->join_room(room_id, [room_id](const nlohmann::json &obj, RequestErr e) {
if (e) {
print_errors(e);
printf("failed to join room %s\n", room_id.to_string().c_str());
return;
}
printf("joined room \n%s\n", obj.dump(2).c_str());
mtx::events::msg::Text text;
text.body = "Thanks for the invitation!";
client->send_room_message<mtx::events::msg::Text,
mtx::events::EventType::RoomMessage>(
room_id, text, [room_id](const mtx::responses::EventId &, RequestErr e) {
if (e) {
print_errors(e);
return;
}
printf("sent message to %s\n", room_id.to_string().c_str());
});
});
}
if (!parse_repeat_cmd)
return;
for (const auto room : res.rooms.join) {
const std::string repeat_cmd = "!repeat";
const std::string room_id = room.first;
for (const auto &e : room.second.timeline.events) {
if (!is_room_message(e))
continue;
auto body = get_body(e);
if (!boost::starts_with(body, repeat_cmd))
continue;
auto word = std::string(body.begin() + repeat_cmd.size(), body.end());
auto user = get_sender(e);
mtx::events::msg::Text text;
text.body = user + ": " + word;
client->send_room_message<mtx::events::msg::Text,
mtx::events::EventType::RoomMessage>(
parse<Room>(room_id),
text,
[room_id](const mtx::responses::EventId &, RequestErr e) {
if (e) {
print_errors(e);
return;
}
printf("sent message to %s\n", room_id.c_str());
});
}
}
}
// Callback to executed after a /sync request completes.
void
sync_handler(const mtx::responses::Sync &res, RequestErr err)
{
SyncOpts opts;
if (err) {
cout << "sync error:\n";
print_errors(err);
opts.since = client->next_batch_token();
client->sync(opts, &sync_handler);
return;
}
parse_messages(res, true);
opts.since = res.next_batch;
client->set_next_batch_token(res.next_batch);
client->sync(opts, &sync_handler);
}
// Callback to executed after the first (initial) /sync request completes.
void
initial_sync_handler(const mtx::responses::Sync &res, RequestErr err)
{
SyncOpts opts;
if (err) {
cout << "error during initial sync:\n";
print_errors(err);
if (err->status_code != boost::beast::http::status::ok) {
cout << "retrying initial sync ...\n";
opts.timeout = 0;
client->sync(opts, &initial_sync_handler);
}
return;
}
parse_messages(res);
opts.since = res.next_batch;
client->set_next_batch_token(res.next_batch);
client->sync(opts, &sync_handler);
}
void
login_handler(const mtx::responses::Login &, RequestErr err)
{
if (err) {
printf("login error\n");
print_errors(err);
return;
}
printf("user_id: %s\n", client->user_id().to_string().c_str());
printf("device_id: %s\n", client->device_id().c_str());
SyncOpts opts;
opts.timeout = 0;
client->sync(opts, &initial_sync_handler);
}
int
main()
{
std::string username, server, password;
cout << "username: ";
std::getline(std::cin, username);
cout << "server: ";
std::getline(std::cin, server);
password = getpass("password: ");
client = std::make_shared<Client>(server);
client->login(username, password, login_handler);
client->close();
return 0;
}
......@@ -69,8 +69,6 @@ public:
//! Wait for the client to close.
void close();
//! Make a new request.
void do_request(std::shared_ptr<Session> session);
//! Add an access token.
void set_access_token(const std::string &token) { access_token_ = token; }
//! Retrieve the access token.
......@@ -270,13 +268,10 @@ private:
void setup_auth(Session *session, bool auth);
boost::asio::io_service ios_;
//! Used to prevent the event loop from shutting down.
boost::optional<boost::asio::io_service::work> work_;
boost::optional<boost::asio::io_context::work> work_{ios_};
//! Worker threads for the requests.
boost::thread_group thread_group_;
//! Used to resolve DNS names.
boost::asio::ip::tcp::resolver resolver_;
//! SSL context for requests.
boost::asio::ssl::context ssl_ctx_{boost::asio::ssl::context::sslv23_client};
//! The homeserver to connect to.
......@@ -313,7 +308,7 @@ mtx::http::Client::post(const std::string &endpoint,
setup_headers<Request, boost::beast::http::verb::post>(
session.get(), req, endpoint, content_type);
do_request(std::move(session));
session->run();
}
// put function for the PUT HTTP requests that send responses
......@@ -334,7 +329,7 @@ mtx::http::Client::put(const std::string &endpoint,
setup_headers<Request, boost::beast::http::verb::put>(
session.get(), req, endpoint, "application/json");
do_request(std::move(session));
session->run();
}
// provides PUT functionality for the endpoints which dont respond with a body
......@@ -366,7 +361,7 @@ mtx::http::Client::get(const std::string &endpoint,
setup_auth(session.get(), requires_auth);
setup_headers<std::string, boost::beast::http::verb::get>(session.get(), {}, endpoint);
do_request(std::move(session));
session->run();
}
template<class Response>
......@@ -374,9 +369,10 @@ std::shared_ptr<mtx::http::Session>
mtx::http::Client::create_session(HeadersCallback<Response> callback)
{
auto session = std::make_shared<Session>(
ios_,
ssl_ctx_,
std::ref(ios_),
std::ref(ssl_ctx_),
server_,
port_,
client::utils::random_token(),
[callback](RequestID,
const boost::beast::http::response<boost::beast::http::string_body> &response,
......@@ -438,23 +434,6 @@ mtx::http::Client::create_session(HeadersCallback<Response> callback)
callback(response_data, {}, client_error);
});
// Set SNI Hostname (many hosts need this to handshake successfully)
if (!SSL_set_tlsext_host_name(session->socket.native_handle(), server_.c_str())) {
boost::system::error_code ec{static_cast<int>(::ERR_get_error()),
boost::asio::error::get_ssl_category()};
std::cerr << ec.message() << "\n";
Response response_data;
mtx::http::ClientError client_error;
client_error.error_code = ec;
callback(response_data, {}, client_error);
// Initialization failed.
return nullptr;
}
return std::move(session);
}
......
......@@ -4,6 +4,7 @@
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include "mtxclient/http/errors.hpp"
#include "mtxclient/utils.hpp"
namespace mtx {
......@@ -28,14 +29,19 @@ struct Session : public std::enable_shared_from_this<Session>
Session(boost::asio::io_service &ios,
boost::asio::ssl::context &ssl_ctx,
const std::string &host,
uint16_t port,
RequestID id,
SuccessCallback on_success,
FailureCallback on_failure);
//! DNS resolver.
boost::asio::ip::tcp::resolver resolver_;
//! Socket used for communication.
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket;
//! Remote host.
std::string host;
//! Remote port.
uint16_t port;
//! Buffer where the response will be stored.
boost::beast::flat_buffer output_buf;
//! Parser that will the response data.
......@@ -52,10 +58,28 @@ struct Session : public std::enable_shared_from_this<Session>
//! Function to be called when the request fails.
FailureCallback on_failure;
void on_resolve(boost::system::error_code ec,
boost::asio::ip::tcp::resolver::results_type results);
void run()
{
// Set SNI Hostname (many hosts need this to handshake successfully)
if (!SSL_set_tlsext_host_name(socket.native_handle(), host.c_str())) {
boost::system::error_code ec{static_cast<int>(::ERR_get_error()),
boost::asio::error::get_ssl_category()};
std::cerr << ec.message() << "\n";
return on_failure(id, ec);
}
resolver_.async_resolve(host,
std::to_string(port),
std::bind(&Session::on_resolve,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
private:
void on_resolve(boost::system::error_code ec,
boost::asio::ip::tcp::resolver::results_type results);
void on_close(boost::system::error_code ec);
void on_connect(const boost::system::error_code &ec);
void on_handshake(const boost::system::error_code &ec);
......
......@@ -12,13 +12,10 @@ using namespace mtx::http;
using namespace boost::beast;
Client::Client(const std::string &server, uint16_t port)
: resolver_{ios_}
, server_{server}
: server_{server}
, port_{port}
{
using namespace boost::asio;
work_ = boost::in_place<io_service::work>(io_service::work(ios_));
const auto threads_num = std::max(1U, std::thread::hardware_concurrency());
for (unsigned int i = 0; i < threads_num; ++i)
......@@ -31,22 +28,12 @@ Client::close()
// Destroy work object. This allows the I/O thread to
// exit the event loop when there are no more pending
// asynchronous operations.
work_ = boost::none;
work_.reset();
// Wait for the worker threads to exit.
thread_group_.join_all();
}
void
Client::do_request(std::shared_ptr<Session> s)
{
resolver_.async_resolve(
server_,
std::to_string(port_),
std::bind(
&Session::on_resolve, std::move(s), std::placeholders::_1, std::placeholders::_2));
}
void
Client::setup_auth(Session *session, bool auth)
{
......
......@@ -5,11 +5,14 @@ using namespace mtx::http;
Session::Session(boost::asio::io_service &ios,
boost::asio::ssl::context &ssl_ctx,
const std::string &host,
uint16_t port,
RequestID id,
SuccessCallback on_success,
FailureCallback on_failure)
: socket(ios, ssl_ctx)
: resolver_(ios)
, socket(ios, ssl_ctx)
, host(std::move(host))
, port{port}
, id(std::move(id))
, on_success(std::move(on_success))
, on_failure(std::move(on_failure))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment