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