From 6b347e7310c501b826785e9639d962ba1d448b4b Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 23 Sep 2020 00:56:54 +0200 Subject: Add matrix image upload --- src/plugins/FileManager.cpp | 35 +++++++--- src/plugins/Matrix.cpp | 152 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 158 insertions(+), 29 deletions(-) (limited to 'src/plugins') diff --git a/src/plugins/FileManager.cpp b/src/plugins/FileManager.cpp index fc6205c..c68bb94 100644 --- a/src/plugins/FileManager.cpp +++ b/src/plugins/FileManager.cpp @@ -18,23 +18,40 @@ namespace QuickMedia { return ""; } + static std::filesystem::file_time_type file_get_filetime_or(const std::filesystem::directory_entry &path, std::filesystem::file_time_type default_value) { + try { + return path.last_write_time(); + } catch(const std::filesystem::filesystem_error &err) { + return default_value; + } + } + PluginResult FileManager::get_files_in_directory(BodyItems &result_items) { + std::vector paths; try { for(auto &p : std::filesystem::directory_iterator(current_dir)) { - auto body_item = std::make_unique(p.path().filename().string()); - if(p.is_regular_file()) { - if(is_image_ext(get_ext(p.path()))) { - body_item->thumbnail_is_local = true; - body_item->thumbnail_url = p.path().string(); - } - } - result_items.push_back(std::move(body_item)); + paths.push_back(p); } - return PluginResult::OK; } catch(const std::filesystem::filesystem_error &err) { fprintf(stderr, "Failed to list files in directory %s, error: %s\n", current_dir.c_str(), err.what()); return PluginResult::ERR; } + + std::sort(paths.begin(), paths.end(), [](const std::filesystem::directory_entry &path1, std::filesystem::directory_entry &path2) { + return file_get_filetime_or(path1, std::filesystem::file_time_type::min()) > file_get_filetime_or(path2, std::filesystem::file_time_type::min()); + }); + + for(auto &p : paths) { + auto body_item = std::make_unique(p.path().filename().string()); + // TODO: Check file magic number instead of extension? + if(p.is_regular_file() && is_image_ext(get_ext(p.path()))) { + body_item->thumbnail_is_local = true; + body_item->thumbnail_url = p.path().string(); + } + result_items.push_back(std::move(body_item)); + } + + return PluginResult::OK; } bool FileManager::set_current_directory(const std::string &path) { diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index ff854c5..e806f39 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -1,5 +1,6 @@ #include "../../plugins/Matrix.hpp" #include "../../include/Storage.hpp" +#include "../../include/ImageUtils.hpp" #include #include #include @@ -557,7 +558,16 @@ namespace QuickMedia { return result.str(); } - PluginResult Matrix::post_message(const std::string &room_id, const std::string &text) { + static const char* message_type_to_request_msg_type_str(MessageType msgtype) { + switch(msgtype) { + case MessageType::TEXT: return "m.text"; + case MessageType::IMAGE: return "m.image"; + case MessageType::VIDEO: return "m.video"; + } + return "m.text"; + } + + PluginResult Matrix::post_message(const std::string &room_id, const std::string &body, const std::string &url, MessageType msgtype, MessageInfo *info) { char random_characters[18]; if(!generate_random_characters(random_characters, sizeof(random_characters))) return PluginResult::ERR; @@ -566,27 +576,40 @@ namespace QuickMedia { std::string formatted_body; bool contains_formatted_text = false; - string_split(text, '\n', [&formatted_body, &contains_formatted_text](const char *str, size_t size){ - if(size > 0 && str[0] == '>') { - std::string line(str, size); - html_escape_sequences(line); - formatted_body += ""; - formatted_body += line; - formatted_body += ""; - contains_formatted_text = true; - } else { - formatted_body.append(str, size); - } - return true; - }); + if(msgtype == MessageType::TEXT) { + string_split(body, '\n', [&formatted_body, &contains_formatted_text](const char *str, size_t size){ + if(size > 0 && str[0] == '>') { + std::string line(str, size); + html_escape_sequences(line); + formatted_body += ""; + formatted_body += line; + formatted_body += ""; + contains_formatted_text = true; + } else { + formatted_body.append(str, size); + } + return true; + }); + } Json::Value request_data(Json::objectValue); - request_data["msgtype"] = "m.text"; - request_data["body"] = text; + request_data["msgtype"] = message_type_to_request_msg_type_str(msgtype); + request_data["body"] = body; if(contains_formatted_text) { request_data["format"] = "org.matrix.custom.html"; request_data["formatted_body"] = std::move(formatted_body); } + if(msgtype == MessageType::IMAGE) { + if(info) { + Json::Value info_json(Json::objectValue); + info_json["size"] = info->size; + info_json["w"] = info->w; + info_json["h"] = info->h; + info_json["mimetype"] = info->mimetype; + request_data["info"] = std::move(info_json); + } + request_data["url"] = url; + } Json::StreamWriterBuilder builder; builder["commentStyle"] = "None"; @@ -599,12 +622,12 @@ namespace QuickMedia { { "--data-binary", Json::writeString(builder, request_data) } }; - char url[512]; - snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/send/m.room.message/m%ld.%.*s", homeserver.c_str(), room_id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str()); - fprintf(stderr, "Post message to |%s|\n", url); + char request_url[512]; + snprintf(request_url, sizeof(request_url), "%s/_matrix/client/r0/rooms/%s/send/m.room.message/m%ld.%.*s", homeserver.c_str(), room_id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str()); + fprintf(stderr, "Post message to |%s|\n", request_url); std::string server_response; - if(download_to_string(url, server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) + if(download_to_string(request_url, server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) return PluginResult::NET_ERR; if(server_response.empty()) @@ -630,6 +653,95 @@ namespace QuickMedia { return PluginResult::OK; } + // Returns empty string on error + static const char* file_get_filename(const std::string &filepath) { + size_t index = filepath.rfind('/'); + if(index == std::string::npos) + return ""; + const char *filename = filepath.c_str() + index + 1; + if(filename[0] == '\0') + return ""; + return filename; + } + + static const char* image_type_to_mimetype(ImageType image_type) { + switch(image_type) { + case ImageType::PNG: return "image/png"; + case ImageType::GIF: return "image/gif"; + case ImageType::JPG: return "image/jpeg"; + } + return "application/octet-stream"; + } + + PluginResult Matrix::post_file(const std::string &room_id, const std::string &filepath) { + int image_width, image_height; + ImageType image_type; + if(!image_get_resolution(filepath, &image_width, &image_height, &image_type)) { + fprintf(stderr, "Failed to get resolution of image: %s. Only image uploads are currently supported\n", filepath.c_str()); + return PluginResult::ERR; + } + + const char *mimetype = image_type_to_mimetype(image_type); + + // TODO: What if the file changes after this? is the file size really needed? + size_t file_size; + if(file_get_size(filepath, &file_size) != 0) { + fprintf(stderr, "Failed to get size of image: %s\n", filepath.c_str()); + return PluginResult::ERR; + } + + // TODO: Check server file limit first: GET https://glowers.club/_matrix/media/r0/config + // and also have a sane limit client-side. + if(file_size > 100 * 1024 * 1024) { + fprintf(stderr, "Upload file size it too large!, max size is currently 100mb\n"); + return PluginResult::ERR; + } + + std::vector additional_args = { + { "-X", "POST" }, + { "-H", std::string("content-type: ") + mimetype }, + { "-H", "Authorization: Bearer " + access_token }, + { "--data-binary", "@" + filepath } + }; + + const char *filename = file_get_filename(filepath); + + char url[512]; + snprintf(url, sizeof(url), "%s/_matrix/media/r0/upload?filename=%s", homeserver.c_str(), filename); + fprintf(stderr, "Upload url: |%s|\n", url); + + std::string server_response; + if(download_to_string(url, server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) + return PluginResult::NET_ERR; + + if(server_response.empty()) + return PluginResult::ERR; + + Json::Value json_root; + Json::CharReaderBuilder json_builder; + std::unique_ptr json_reader(json_builder.newCharReader()); + std::string json_errors; + if(!json_reader->parse(&server_response[0], &server_response[server_response.size()], &json_root, &json_errors)) { + fprintf(stderr, "Matrix upload response parse error: %s\n", json_errors.c_str()); + return PluginResult::ERR; + } + + if(!json_root.isObject()) + return PluginResult::ERR; + + const Json::Value &content_uri_json = json_root["content_uri"]; + if(!content_uri_json.isString()) + return PluginResult::ERR; + + fprintf(stderr, "Matrix upload, response content uri: %s\n", content_uri_json.asCString()); + MessageInfo message_info; + message_info.size = file_size; + message_info.w = image_width; + message_info.h = image_height; + message_info.mimetype = mimetype; + return post_message(room_id, filename, content_uri_json.asString(), MessageType::IMAGE, &message_info); + } + static std::string parse_login_error_response(std::string json_str) { if(json_str.empty()) return "Unknown error"; -- cgit v1.2.3