From 3f3082e13d37c3adcece68cdcf8cea13591134a0 Mon Sep 17 00:00:00 2001 From: Nicolas Werner <nicolas.werner@hotmail.de> Date: Sun, 15 Dec 2019 23:46:45 +0100 Subject: [PATCH] Add example, that downloads all unencrypted media in a room --- examples/CMakeLists.txt | 3 + examples/media_downloader.cpp | 237 ++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 examples/media_downloader.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 85ef0925f..292c66869 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -10,6 +10,9 @@ include_directories(../tests) add_executable(room_feed room_feed.cpp) target_link_libraries(room_feed MatrixClient::MatrixClient) +add_executable(media_downloader media_downloader.cpp) +target_link_libraries(media_downloader MatrixClient::MatrixClient) + add_executable(simple_bot simple_bot.cpp) target_link_libraries(simple_bot MatrixClient::MatrixClient) diff --git a/examples/media_downloader.cpp b/examples/media_downloader.cpp new file mode 100644 index 000000000..52741c090 --- /dev/null +++ b/examples/media_downloader.cpp @@ -0,0 +1,237 @@ +#include <boost/beast.hpp> +#include <filesystem> +#include <fstream> +#include <iostream> +#include <unistd.h> +#include <variant> + +#include "mtx.hpp" +#include "mtxclient/http/client.hpp" +#include "mtxclient/http/errors.hpp" + +// +// Simple usage example of the /login & /sync endpoints which +// will print the stream of messages from all rooms as received by the client. +// + +using namespace std; +using namespace mtx::client; +using namespace mtx::http; +using namespace mtx::events; + +using TimelineEvent = mtx::events::collections::TimelineEvents; + +std::string room_id_to_download_from; + +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 &e) +{ + return (std::holds_alternative<mtx::events::RoomEvent<msg::Audio>>(e)) || + (std::holds_alternative<mtx::events::RoomEvent<msg::Emote>>(e)) || + (std::holds_alternative<mtx::events::RoomEvent<msg::File>>(e)) || + (std::holds_alternative<mtx::events::RoomEvent<msg::Image>>(e)) || + (std::holds_alternative<mtx::events::RoomEvent<msg::Notice>>(e)) || + (std::holds_alternative<mtx::events::RoomEvent<msg::Text>>(e)) || + (std::holds_alternative<mtx::events::RoomEvent<msg::Video>>(e)); +} + +// Retrieves the fallback body value from the event. +std::string +get_body(const TimelineEvent &e) +{ + if (auto ev = std::get_if<RoomEvent<msg::Audio>>(&e); ev != nullptr) + return ev->content.body; + else if (auto ev = std::get_if<RoomEvent<msg::Emote>>(&e); ev != nullptr) + return ev->content.body; + else if (auto ev = std::get_if<RoomEvent<msg::File>>(&e); ev != nullptr) + return ev->content.body; + else if (auto ev = std::get_if<RoomEvent<msg::Image>>(&e); ev != nullptr) + return ev->content.body; + else if (auto ev = std::get_if<RoomEvent<msg::Notice>>(&e); ev != nullptr) + return ev->content.body; + else if (auto ev = std::get_if<RoomEvent<msg::Text>>(&e); ev != nullptr) + return ev->content.body; + else if (auto ev = std::get_if<RoomEvent<msg::Video>>(&e); ev != nullptr) + return ev->content.body; + + return ""; +} + +// Retrieves the fallback body value from the event. +std::string +get_url(const TimelineEvent &e) +{ + if (auto ev = std::get_if<RoomEvent<msg::Audio>>(&e); ev != nullptr) + return ev->content.url; + else if (auto ev = std::get_if<RoomEvent<msg::File>>(&e); ev != nullptr) + return ev->content.url; + else if (auto ev = std::get_if<RoomEvent<msg::Image>>(&e); ev != nullptr) + return ev->content.url; + else if (auto ev = std::get_if<RoomEvent<msg::Video>>(&e); ev != nullptr) + return ev->content.url; + + return ""; +} + +// Retrieves the sender of the event. +std::string +get_sender(const TimelineEvent &event) +{ + return std::visit([](auto e) { return e.sender; }, event); +} + +// Simple print of the message contents. +void +print_message(const TimelineEvent &event) +{ + if (is_room_message(event)) + cout << get_sender(event) << ": " << get_body(event) << "\n"; + + if (auto url = get_url(event); !url.empty()) { + cout << "found url: " << url << "\n"; + + client->download(url, + [url](const std::string &data, + const std::string &content_type, + const std::string &original_filename, + RequestErr err) { + if (err) { + cout << "download error:\n"; + print_errors(err); + return; + } + std::string_view urlv = url; + urlv.remove_prefix(std::size("mxc://") - 1); + std::filesystem::path p(urlv); + std::filesystem::create_directories(p.parent_path()); + + ofstream file(p, std::ios::binary); + file.write(data.data(), data.size()); + + if (file.good()) + cout << "Wrote to: " << p + << ", original filename was '" + << original_filename << "', content_type '" + << content_type << "'\n"; + else + cout << "Write to '" << p << "' failed!\n"; + }); + } +} + +// Callback to executed after a /sync request completes. +void +message_handler(const mtx::responses::Messages &res, RequestErr err) +{ + MessagesOpts opts; + opts.room_id = room_id_to_download_from; + opts.limit = 300; + opts.from = res.end; + + if (err) { + cout << "messages error:\n"; + print_errors(err); + opts.from = res.start; + client->messages(opts, &message_handler); + return; + } + + for (const auto msg : res.chunk) + print_message(msg); + + if (res.chunk.empty()) + { + cout << "No more messages, exiting.\n"; + return; + } + + + client->messages(opts, &message_handler); +} + +// Callback to executed after the first (initial) /sync request completes. +void +initial_sync_handler(const mtx::responses::Sync &res, RequestErr err) +{ + MessagesOpts opts; + opts.room_id = room_id_to_download_from; + opts.limit = 300; + + 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"; + SyncOpts sopts; + sopts.timeout = 0; + client->sync(sopts, &initial_sync_handler); + } + + return; + } + + if (res.rooms.join.count(opts.room_id) == 0) { + cout << "room doesn't exist?"; + return; + } + + opts.from = res.next_batch; + client->messages(opts, message_handler); +} + +void +login_handler(const mtx::responses::Login &res, RequestErr err) +{ + if (err) { + cout << "There was an error during login: " << err->matrix_error.error << "\n"; + return; + } + + cout << "Logged in as: " << res.user_id.to_string() << "\n"; + + cout << "give room id to download all media from: "; + std::getline(std::cin, room_id_to_download_from); + + SyncOpts opts; + opts.timeout = 0; + + client->set_access_token(res.access_token); + client->sync(opts, &initial_sync_handler); +} + +int +main() +{ + std::string username, server, password; + + cout << "Username: "; + std::getline(std::cin, username); + + cout << "HomeServer: "; + std::getline(std::cin, server); + + password = getpass("Password: "); + + client = std::make_shared<Client>(server); + client->login(username, password, &login_handler); + client->close(); + + return 0; +} -- GitLab