aboutsummaryrefslogtreecommitdiff
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/Dmenu.cpp2
-rw-r--r--src/plugins/Fourchan.cpp8
-rw-r--r--src/plugins/Matrix.cpp687
-rw-r--r--src/plugins/NyaaSi.cpp2
-rw-r--r--src/plugins/Plugin.cpp37
5 files changed, 717 insertions, 19 deletions
diff --git a/src/plugins/Dmenu.cpp b/src/plugins/Dmenu.cpp
index a3b354b..5c46841 100644
--- a/src/plugins/Dmenu.cpp
+++ b/src/plugins/Dmenu.cpp
@@ -16,7 +16,7 @@ namespace QuickMedia {
return PluginResult::OK;
}
- SearchResult Dmenu::search(const std::string &text, BodyItems &result_items) {
+ SearchResult Dmenu::search(const std::string &text, BodyItems&) {
std::cout << text << std::endl;
return SearchResult::OK;
}
diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp
index 4f87049..4490f39 100644
--- a/src/plugins/Fourchan.cpp
+++ b/src/plugins/Fourchan.cpp
@@ -112,14 +112,6 @@ namespace QuickMedia {
return PluginResult::OK;
}
- SearchResult Fourchan::search(const std::string &url, BodyItems &result_items) {
- return SearchResult::OK;
- }
-
- SuggestionResult Fourchan::update_search_suggestions(const std::string &text, BodyItems &result_items) {
- return SuggestionResult::OK;
- }
-
struct CommentPiece {
enum class Type {
TEXT,
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
new file mode 100644
index 0000000..77e295e
--- /dev/null
+++ b/src/plugins/Matrix.cpp
@@ -0,0 +1,687 @@
+#include "../../plugins/Matrix.hpp"
+#include "../../include/Storage.hpp"
+#include <json/reader.h>
+#include <json/writer.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+// TODO: Update avatar/display name when its changed in the room/globally.
+// Send read receipt to server and receive notifications in /sync and show the notifications.
+// Delete messages.
+// Edit messages.
+// Show embedded images/videos.
+// TODO: Verify if buffer of size 512 is enough for endpoints
+
+namespace QuickMedia {
+ Matrix::Matrix() : Plugin("matrix") {
+
+ }
+
+ PluginResult Matrix::get_cached_sync(BodyItems &result_items) {
+ /*
+ Path sync_cache_path = get_cache_dir().join(name).join("sync.json");
+ Json::Value root;
+ if(!read_file_as_json(sync_cache_path, root))
+ return PluginResult::ERR;
+ return sync_response_to_body_items(root, result_items);
+ */
+ (void)result_items;
+ return PluginResult::OK;
+ }
+
+ PluginResult Matrix::sync() {
+ std::vector<CommandArg> additional_args = {
+ { "-H", "Authorization: Bearer " + access_token },
+ { "-m", "35" }
+ };
+
+ std::string server_response;
+ // timeout=30000, filter=0. First sync should be without filter and timeout=0, then all other sync should be with timeout=30000 and filter=0.
+ // GET https://glowers.club/_matrix/client/r0/user/%40dec05eba%3Aglowers.club/filter/0 first to check if the filter is available
+ // and if lazy load members is available and get limit to use with https://glowers.club/_matrix/client/r0/rooms/!oSXkiqBKooDcZsmiGO%3Aglowers.club/
+ // when first launching the client. This call to /rooms/ should be called before /sync/, when accessing a room. But only the first time
+ // (for the session).
+
+ // Note: the first sync call with always exclude since= (next_batch) because we want to receive the latest messages in a room,
+ // which is important if we for example login to matrix after having not been online for several days and there are many new messages.
+ // We should be shown the latest messages first and if the user wants to see older messages then they should scroll up.
+ // Note: missed mentions are received in /sync and they will remain in new /sync unless we send a read receipt that we have read them.
+
+ char url[512];
+ if(next_batch.empty())
+ snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=0", homeserver.c_str());
+ else
+ snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=30000&since=%s", homeserver.c_str(), next_batch.c_str());
+
+ 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::CharReader> 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 sync response parse error: %s\n", json_errors.c_str());
+ return PluginResult::ERR;
+ }
+
+ PluginResult result = sync_response_to_body_items(json_root);
+ if(result != PluginResult::OK)
+ return result;
+
+ const Json::Value &next_batch_json = json_root["next_batch"];
+ if(next_batch_json.isString()) {
+ next_batch = next_batch_json.asString();
+ fprintf(stderr, "Matrix: next batch: %s\n", next_batch.c_str());
+ } else {
+ fprintf(stderr, "Matrix: missing next batch\n");
+ }
+
+ // TODO: Only create the first time sync is called?
+ /*
+ Path sync_cache_path = get_cache_dir().join(name);
+ if(create_directory_recursive(sync_cache_path) == 0) {
+ sync_cache_path.join("sync.json");
+ if(!save_json_to_file_atomic(sync_cache_path, json_root)) {
+ fprintf(stderr, "Warning: failed to save sync response to %s\n", sync_cache_path.data.c_str());
+ }
+ } else {
+ fprintf(stderr, "Warning: failed to create directory: %s\n", sync_cache_path.data.c_str());
+ }
+ */
+
+ return PluginResult::OK;
+ }
+
+ PluginResult Matrix::get_joined_rooms(BodyItems &result_items) {
+ std::vector<CommandArg> additional_args = {
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ std::string server_response;
+ if(download_to_string(homeserver + "/_matrix/client/r0/joined_rooms", 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::CharReader> 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 joined rooms response parse error: %s\n", json_errors.c_str());
+ return PluginResult::ERR;
+ }
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &joined_rooms_json = json_root["joined_rooms"];
+ if(!joined_rooms_json.isArray())
+ return PluginResult::ERR;
+
+ for(const Json::Value &room_id_json : joined_rooms_json) {
+ if(!room_id_json.isString())
+ continue;
+
+ std::string room_id_str = room_id_json.asString();
+
+ auto room_it = room_data_by_id.find(room_id_str);
+ if(room_it == room_data_by_id.end()) {
+ auto room_data = std::make_unique<RoomData>();
+ room_data_by_id.insert(std::make_pair(room_id_str, std::move(room_data)));
+ fprintf(stderr, "Missing room %s from /sync, adding in joined_rooms\n", room_id_str.c_str());
+ }
+
+ auto body_item = std::make_unique<BodyItem>(std::move(room_id_str));
+ //body_item->url = "";
+ result_items.push_back(std::move(body_item));
+ }
+
+ return PluginResult::OK;
+ }
+
+ PluginResult Matrix::get_room_messages(const std::string &room_id, size_t start_index, BodyItems &result_items, size_t &num_new_messages) {
+ num_new_messages = 0;
+
+ auto room_it = room_data_by_id.find(room_id);
+ if(room_it == room_data_by_id.end()) {
+ fprintf(stderr, "Error: no such room: %s\n", room_id.c_str());
+ return PluginResult::ERR;
+ }
+
+ if(!room_it->second->initial_fetch_finished) {
+ PluginResult result = load_initial_room_data(room_id, room_it->second.get());
+ if(result == PluginResult::OK) {
+ room_it->second->initial_fetch_finished = true;
+ } else {
+ fprintf(stderr, "Initial sync failed for room: %s\n", room_id.c_str());
+ return result;
+ }
+ }
+
+ // This will happen if there are no new messages
+ if(start_index >= room_it->second->messages.size())
+ return PluginResult::OK;
+
+ num_new_messages = room_it->second->messages.size() - start_index;
+
+ size_t prev_user_id = -1;
+ for(auto it = room_it->second->messages.begin() + start_index, end = room_it->second->messages.end(); it != end; ++it) {
+ const UserInfo &user_info = room_it->second->user_info[it->user_id];
+ if(it->user_id == prev_user_id) {
+ assert(!result_items.empty());
+ result_items.back()->append_description("\n");
+ result_items.back()->append_description(it->msg);
+ } else {
+ auto body_item = std::make_unique<BodyItem>("");
+ body_item->author = user_info.display_name;
+ body_item->set_description(it->msg);
+ body_item->thumbnail_url = user_info.avatar_url;
+ result_items.push_back(std::move(body_item));
+ prev_user_id = it->user_id;
+ }
+ }
+
+ return PluginResult::OK;
+ }
+
+ PluginResult Matrix::sync_response_to_body_items(const Json::Value &root) {
+ if(!root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &rooms_json = root["rooms"];
+ if(!rooms_json.isObject())
+ return PluginResult::OK;
+
+ const Json::Value &join_json = rooms_json["join"];
+ if(!join_json.isObject())
+ return PluginResult::OK;
+
+ for(Json::Value::const_iterator it = join_json.begin(); it != join_json.end(); ++it) {
+ if(!it->isObject())
+ continue;
+
+ Json::Value room_id = it.key();
+ if(!room_id.isString())
+ continue;
+
+ std::string room_id_str = room_id.asString();
+
+ auto room_it = room_data_by_id.find(room_id_str);
+ if(room_it == room_data_by_id.end()) {
+ auto room_data = std::make_unique<RoomData>();
+ room_data_by_id.insert(std::make_pair(room_id_str, std::move(room_data)));
+ room_it = room_data_by_id.find(room_id_str); // TODO: Get iterator from above insert
+ }
+
+ const Json::Value &state_json = (*it)["state"];
+ if(!state_json.isObject())
+ continue;
+
+ const Json::Value &events_json = state_json["events"];
+ events_add_user_info(events_json, room_it->second.get());
+ }
+
+ for(Json::Value::const_iterator it = join_json.begin(); it != join_json.end(); ++it) {
+ if(!it->isObject())
+ continue;
+
+ Json::Value room_id = it.key();
+ if(!room_id.isString())
+ continue;
+
+ std::string room_id_str = room_id.asString();
+
+ auto room_it = room_data_by_id.find(room_id_str);
+ if(room_it == room_data_by_id.end()) {
+ auto room_data = std::make_unique<RoomData>();
+ room_data_by_id.insert(std::make_pair(room_id_str, std::move(room_data)));
+ room_it = room_data_by_id.find(room_id_str); // TODO: Get iterator from above insert
+ }
+
+ const Json::Value &timeline_json = (*it)["timeline"];
+ if(!timeline_json.isObject())
+ continue;
+
+ // This may be non-existent if this is the first event in the room
+ const Json::Value &prev_batch_json = timeline_json["prev_batch"];
+ if(prev_batch_json.isString())
+ room_it->second->prev_batch = prev_batch_json.asString();
+
+ const Json::Value &events_json = timeline_json["events"];
+ events_add_messages(events_json, room_it->second.get(), MessageDirection::AFTER);
+ }
+
+ return PluginResult::OK;
+ }
+
+ void Matrix::events_add_user_info(const Json::Value &events_json, RoomData *room_data) {
+ if(!events_json.isArray())
+ return;
+
+ for(const Json::Value &event_item_json : events_json) {
+ if(!event_item_json.isObject())
+ continue;
+
+ const Json::Value &type_json = event_item_json["type"];
+ if(!type_json.isString() || strcmp(type_json.asCString(), "m.room.member") != 0)
+ continue;
+
+ const Json::Value &sender_json = event_item_json["sender"];
+ if(!sender_json.isString())
+ continue;
+
+ const Json::Value &content_json = event_item_json["content"];
+ if(!content_json.isObject())
+ continue;
+
+ const Json::Value &membership_json = content_json["membership"];
+ if(!membership_json.isString() || strcmp(membership_json.asCString(), "join") != 0)
+ continue;
+
+ const Json::Value &avatar_url_json = content_json["avatar_url"];
+ if(!avatar_url_json.isString())
+ continue;
+
+ const Json::Value &display_name_json = content_json["displayname"];
+ if(!display_name_json.isString())
+ continue;
+
+ std::string sender_json_str = sender_json.asString();
+ auto user_it = room_data->user_info_by_user_id.find(sender_json_str);
+ if(user_it != room_data->user_info_by_user_id.end())
+ continue;
+
+ UserInfo user_info;
+ user_info.avatar_url = avatar_url_json.asString();
+ if(user_info.avatar_url.size() >= 6)
+ user_info.avatar_url.erase(user_info.avatar_url.begin(), user_info.avatar_url.begin() + 6);
+ // TODO: What if the user hasn't selected an avatar?
+ user_info.avatar_url = homeserver + "/_matrix/media/r0/thumbnail/" + user_info.avatar_url + "?width=32&height=32&method=crop";
+ user_info.display_name = display_name_json.asString();
+ room_data->user_info.push_back(std::move(user_info));
+ room_data->user_info_by_user_id.insert(std::make_pair(sender_json_str, room_data->user_info.size() - 1));
+ }
+ }
+
+ void Matrix::events_add_messages(const Json::Value &events_json, RoomData *room_data, MessageDirection message_dir) {
+ if(!events_json.isArray())
+ return;
+
+ std::vector<Message> new_messages;
+
+ for(const Json::Value &event_item_json : events_json) {
+ if(!event_item_json.isObject())
+ continue;
+
+ const Json::Value &type_json = event_item_json["type"];
+ if(!type_json.isString() || strcmp(type_json.asCString(), "m.room.message") != 0)
+ continue;
+
+ const Json::Value &sender_json = event_item_json["sender"];
+ if(!sender_json.isString())
+ continue;
+
+ std::string sender_json_str = sender_json.asString();
+
+ const Json::Value &content_json = event_item_json["content"];
+ if(!content_json.isObject())
+ continue;
+
+ const Json::Value &content_type = content_json["msgtype"];
+ if(!content_type.isString() || strcmp(content_type.asCString(), "m.text") != 0)
+ continue;
+
+ const Json::Value &body_json = content_json["body"];
+ if(!body_json.isString())
+ continue;
+
+ auto user_it = room_data->user_info_by_user_id.find(sender_json_str);
+ if(user_it == room_data->user_info_by_user_id.end()) {
+ fprintf(stderr, "Warning: skipping unknown user: %s\n", sender_json_str.c_str());
+ continue;
+ }
+
+ Message message;
+ message.user_id = user_it->second;
+ message.msg = body_json.asString();
+ new_messages.push_back(std::move(message));
+ }
+
+ if(message_dir == MessageDirection::BEFORE) {
+ room_data->messages.insert(room_data->messages.begin(), new_messages.rbegin(), new_messages.rend());
+ } else if(message_dir == MessageDirection::AFTER) {
+ room_data->messages.insert(room_data->messages.end(), new_messages.begin(), new_messages.end());
+ }
+ }
+
+ PluginResult Matrix::load_initial_room_data(const std::string &room_id, RoomData *room_data) {
+ std::string from = room_data->prev_batch;
+ if(from.empty()) {
+ fprintf(stderr, "Info: missing previous batch for room: %s, using /sync next batch\n", room_id.c_str());
+ from = next_batch;
+ if(from.empty()) {
+ fprintf(stderr, "Error: missing next batch!\n");
+ return PluginResult::OK;
+ }
+ }
+
+ Json::Value request_data(Json::objectValue);
+ request_data["lazy_load_members"] = true;
+
+ Json::StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = "";
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ std::string filter = url_param_encode(Json::writeString(builder, request_data));
+
+ char url[512];
+ snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/messages?from=%s&limit=20&dir=b&filter=%s", homeserver.c_str(), room_id.c_str(), from.c_str(), filter.c_str());
+ fprintf(stderr, "load initial room data, 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::CharReader> 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 /rooms/<room_id>/messages/ response parse error: %s\n", json_errors.c_str());
+ return PluginResult::ERR;
+ }
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &state_json = json_root["state"];
+ events_add_user_info(state_json, room_data);
+
+ const Json::Value &chunk_json = json_root["chunk"];
+ events_add_messages(chunk_json, room_data, MessageDirection::BEFORE);
+
+ return PluginResult::OK;
+ }
+
+ SearchResult Matrix::search(const std::string&, BodyItems&) {
+ return SearchResult::OK;
+ }
+
+ static bool generate_random_characters(char *buffer, int buffer_size) {
+ int fd = open("/dev/urandom", O_RDONLY);
+ if(fd == -1) {
+ perror("/dev/urandom");
+ return false;
+ }
+
+ if(read(fd, buffer, buffer_size) < buffer_size) {
+ fprintf(stderr, "Failed to read %d bytes from /dev/urandom\n", buffer_size);
+ close(fd);
+ return false;
+ }
+
+ close(fd);
+ return true;
+ }
+
+ static std::string random_characters_to_readable_string(const char *buffer, int buffer_size) {
+ std::ostringstream result;
+ result << std::hex;
+ for(int i = 0; i < buffer_size; ++i)
+ result << (int)(unsigned char)buffer[i];
+ return result.str();
+ }
+
+ PluginResult Matrix::post_message(const std::string &room_id, const std::string &text) {
+ char random_characters[18];
+ if(!generate_random_characters(random_characters, sizeof(random_characters)))
+ return PluginResult::ERR;
+
+ std::string random_readable_chars = random_characters_to_readable_string(random_characters, sizeof(random_characters));
+
+ 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 += "<font color=\"#789922\">";
+ formatted_body += line;
+ formatted_body += "</font>";
+ 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;
+ if(contains_formatted_text) {
+ request_data["format"] = "org.matrix.custom.html";
+ request_data["formatted_body"] = std::move(formatted_body);
+ }
+
+ Json::StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = "";
+
+ std::vector<CommandArg> additional_args = {
+ { "-X", "PUT" },
+ { "-H", "content-type: application/json" },
+ { "-H", "Authorization: Bearer " + access_token },
+ { "--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);
+
+ 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::CharReader> 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 post message response parse error: %s\n", json_errors.c_str());
+ return PluginResult::ERR;
+ }
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &event_id_json = json_root["event_id"];
+ if(!event_id_json.isString())
+ return PluginResult::ERR;
+
+ fprintf(stderr, "Matrix post message, response event id: %s\n", event_id_json.asCString());
+ return PluginResult::OK;
+ }
+
+ static std::string parse_login_error_response(std::string json_str) {
+ if(json_str.empty())
+ return "Unknown error";
+
+ Json::Value json_root;
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
+ std::string json_errors;
+ if(!json_reader->parse(&json_str[0], &json_str[json_str.size()], &json_root, &json_errors)) {
+ fprintf(stderr, "Matrix login response parse error: %s\n", json_errors.c_str());
+ return json_str;
+ }
+
+ if(!json_root.isObject())
+ return json_str;
+
+ const Json::Value &errcode_json = json_root["errcode"];
+ // Yes, matrix is retarded and returns M_NOT_JSON error code when username/password is incorrect
+ if(errcode_json.isString() && strcmp(errcode_json.asCString(), "M_NOT_JSON") == 0)
+ return "Incorrect username or password";
+
+ return json_str;
+ }
+
+ // Returns empty string on error
+ static std::string extract_homeserver_from_user_id(const std::string &user_id) {
+ size_t index = user_id.find(':');
+ if(index == std::string::npos)
+ return "";
+ return user_id.substr(index + 1);
+ }
+
+ PluginResult Matrix::login(const std::string &username, const std::string &password, const std::string &homeserver, std::string &err_msg) {
+ // TODO: this is deprecated but not all homeservers have the new version.
+ // When this is removed from future version then switch to the new login method (identifier object with the username).
+ Json::Value request_data(Json::objectValue);
+ request_data["type"] = "m.login.password";
+ request_data["user"] = username;
+ request_data["password"] = password;
+
+ Json::StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = "";
+
+ std::vector<CommandArg> additional_args = {
+ { "-X", "POST" },
+ { "-H", "content-type: application/json" },
+ { "--data-binary", Json::writeString(builder, request_data) }
+ };
+
+ std::string server_response;
+ if(download_to_string(homeserver + "/_matrix/client/r0/login", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) {
+ err_msg = parse_login_error_response(std::move(server_response));
+ return PluginResult::NET_ERR;
+ }
+
+ if(server_response.empty())
+ return PluginResult::ERR;
+
+ Json::Value json_root;
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> 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)) {
+ err_msg = "Matrix login response parse error: " + json_errors;
+ return PluginResult::ERR;
+ }
+
+ if(!json_root.isObject()) {
+ err_msg = "Failed to parse matrix login response";
+ return PluginResult::ERR;
+ }
+
+ const Json::Value &user_id_json = json_root["user_id"];
+ if(!user_id_json.isString()) {
+ err_msg = "Failed to parse matrix login response";
+ return PluginResult::ERR;
+ }
+
+ const Json::Value &access_token_json = json_root["access_token"];
+ if(!access_token_json.isString()) {
+ err_msg = "Failed to parse matrix login response";
+ return PluginResult::ERR;
+ }
+
+ std::string user_id = user_id_json.asString();
+
+ std::string homeserver_response = extract_homeserver_from_user_id(user_id);
+ if(homeserver_response.empty()) {
+ err_msg = "Missing homeserver in user id, user id: " + user_id;
+ return PluginResult::ERR;
+ }
+
+ this->user_id = std::move(user_id);
+ this->access_token = access_token_json.asString();
+ this->homeserver = "https://" + std::move(homeserver_response);
+
+ // TODO: Handle well_known field. The spec says clients SHOULD handle it if its provided
+
+ Path session_path = get_storage_dir().join(name);
+ if(create_directory_recursive(session_path) == 0) {
+ session_path.join("session.json");
+ if(!save_json_to_file_atomic(session_path, json_root)) {
+ fprintf(stderr, "Warning: failed to save login response to %s\n", session_path.data.c_str());
+ }
+ } else {
+ fprintf(stderr, "Warning: failed to create directory: %s\n", session_path.data.c_str());
+ }
+
+ return PluginResult::OK;
+ }
+
+ PluginResult Matrix::load_and_verify_cached_session() {
+ Path session_path = get_storage_dir().join(name).join("session.json");
+ std::string session_json_content;
+ if(file_get_content(session_path, session_json_content) != 0) {
+ fprintf(stderr, "Info: failed to read matrix session from %s. Either its missing or we failed to read the file\n", session_path.data.c_str());
+ return PluginResult::ERR;
+ }
+
+ Json::Value json_root;
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
+ std::string json_errors;
+ if(!json_reader->parse(&session_json_content[0], &session_json_content[session_json_content.size()], &json_root, &json_errors)) {
+ fprintf(stderr, "Matrix cached session parse error: %s\n", json_errors.c_str());
+ return PluginResult::ERR;
+ }
+
+ const Json::Value &user_id_json = json_root["user_id"];
+ if(!user_id_json.isString()) {
+ fprintf(stderr, "Failed to parse matrix cached session response\n");
+ return PluginResult::ERR;
+ }
+
+ const Json::Value &access_token_json = json_root["access_token"];
+ if(!access_token_json.isString()) {
+ fprintf(stderr, "Failed to parse matrix cached session response\n");
+ return PluginResult::ERR;
+ }
+
+ std::string user_id = user_id_json.asString();
+ std::string access_token = access_token_json.asString();
+
+ std::string homeserver = extract_homeserver_from_user_id(user_id);
+ if(homeserver.empty()) {
+ fprintf(stderr, "Missing homeserver in user id, user id: %s\n", user_id.c_str());
+ return PluginResult::ERR;
+ }
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ std::string server_response;
+ // We want to make any request to the server that can verify that our token is still valid, doesn't matter which call
+ if(download_to_string("https://" + homeserver + "/_matrix/client/r0/account/whoami", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) {
+ fprintf(stderr, "Matrix whoami response: %s\n", server_response.c_str());
+ return PluginResult::NET_ERR;
+ }
+
+ this->user_id = std::move(user_id);
+ this->access_token = std::move(access_token);
+ this->homeserver = "https://" + homeserver;
+ return PluginResult::OK;
+ }
+} \ No newline at end of file
diff --git a/src/plugins/NyaaSi.cpp b/src/plugins/NyaaSi.cpp
index 2ecf0d3..862d3d4 100644
--- a/src/plugins/NyaaSi.cpp
+++ b/src/plugins/NyaaSi.cpp
@@ -162,7 +162,7 @@ namespace QuickMedia {
// return url.substr(index);
// }
- PluginResult NyaaSi::get_content_details(const std::string &list_url, const std::string &url, BodyItems &result_items) {
+ PluginResult NyaaSi::get_content_details(const std::string&, const std::string &url, BodyItems &result_items) {
size_t comments_start_index;
// std::string id = view_url_get_id(url);
// if(id.empty()) {
diff --git a/src/plugins/Plugin.cpp b/src/plugins/Plugin.cpp
index 8690964..f23175c 100644
--- a/src/plugins/Plugin.cpp
+++ b/src/plugins/Plugin.cpp
@@ -29,22 +29,41 @@ namespace QuickMedia {
}
struct HtmlEscapeSequence {
+ char unescape_char;
std::string escape_sequence;
- std::string unescaped_str;
};
- void html_unescape_sequences(std::string &str) {
+ void html_escape_sequences(std::string &str) {
const std::array<HtmlEscapeSequence, 6> escape_sequences = {
- HtmlEscapeSequence { "&quot;", "\"" },
- HtmlEscapeSequence { "&#039;", "'" },
- HtmlEscapeSequence { "&#39;", "'" },
- HtmlEscapeSequence { "&lt;", "<" },
- HtmlEscapeSequence { "&gt;", ">" },
- HtmlEscapeSequence { "&amp;", "&" } // This should be last, to not accidentally replace a new sequence caused by replacing this
+ HtmlEscapeSequence { '"', "&quot;" },
+ HtmlEscapeSequence { '\'', "&#39;" },
+ HtmlEscapeSequence { '<', "&lt;" },
+ HtmlEscapeSequence { '>', "&gt;" },
+ HtmlEscapeSequence { '&', "&amp;" } // This should be last, to not accidentally replace a new sequence caused by replacing this
};
for(const HtmlEscapeSequence &escape_sequence : escape_sequences) {
- string_replace_all(str, escape_sequence.escape_sequence, escape_sequence.unescaped_str);
+ string_replace_all(str, escape_sequence.unescape_char, escape_sequence.escape_sequence);
+ }
+ }
+
+ struct HtmlUnescapeSequence {
+ std::string escape_sequence;
+ std::string unescaped_str;
+ };
+
+ void html_unescape_sequences(std::string &str) {
+ const std::array<HtmlUnescapeSequence, 6> unescape_sequences = {
+ HtmlUnescapeSequence { "&quot;", "\"" },
+ HtmlUnescapeSequence { "&#039;", "'" },
+ HtmlUnescapeSequence { "&#39;", "'" },
+ HtmlUnescapeSequence { "&lt;", "<" },
+ HtmlUnescapeSequence { "&gt;", ">" },
+ HtmlUnescapeSequence { "&amp;", "&" } // This should be last, to not accidentally replace a new sequence caused by replacing this
+ };
+
+ for(const HtmlUnescapeSequence &unescape_sequence : unescape_sequences) {
+ string_replace_all(str, unescape_sequence.escape_sequence, unescape_sequence.unescaped_str);
}
}