aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-07-26 17:35:52 +0200
committerdec05eba <dec05eba@protonmail.com>2021-07-26 17:35:52 +0200
commita6bba48faa091932b5a51a3beb8c9d162c377adf (patch)
tree9b7e2de493e04e8eaf291f981f70d273b2dd7baf
parent9775a6bb77930a6e3b60445675990a8c01777aea (diff)
Matrix: add /join and /invite commands
-rw-r--r--README.md2
-rw-r--r--TODO6
-rw-r--r--include/Page.hpp3
-rw-r--r--plugins/Matrix.hpp18
-rw-r--r--src/Body.cpp2
-rw-r--r--src/QuickMedia.cpp40
-rw-r--r--src/plugins/Matrix.cpp178
7 files changed, 219 insertions, 30 deletions
diff --git a/README.md b/README.md
index 6322cac..be3d3a1 100644
--- a/README.md
+++ b/README.md
@@ -132,6 +132,8 @@ Type text and then wait and QuickMedia will automatically search.\
`Esc`/`Click on cancel`: Cancel download.
## Matrix text commands
`/upload`: Bring up the file manager and select a file to upload to the room, `Esc` to cancel.\
+`/join [room]`: Join a room by name or id.\
+`/invite`: Invite a user to the room.\
`/logout`: Logout.\
`/leave`: Leave the current room.\
`/me [text]`: Send a message of type "m.emote".\
diff --git a/TODO b/TODO
index 38b126e..95fb812 100644
--- a/TODO
+++ b/TODO
@@ -176,4 +176,8 @@ Allow resuming downloads.
Support downloading live youtube videos.
Youtube broke age restricted video again. Need to find a fix. It kinda works in yt-dlp, but not always.
Use the new player innertube api. To make that work quickmedia will need to extract signatureTimestamp (sts) and set that in the form request. Youtube-dl does this.
-Instead of resetting text items in body, add a clear function to text. That way we can easily cache the height of the text. \ No newline at end of file
+Instead of resetting text items in body, add a clear function to text. That way we can easily cache the height of the text.
+Check if message edits that are replies to me makes a now notification show up. This shouldn't happen, but also take into consideration initial sync.
+Check if user has invite privileges and show error before bringing up invite gui when using /invite.
+If only users in the same homeserver can join a room then filter out other users in room invite gui.
+Exclude users that are already in the room from room invite gui. \ No newline at end of file
diff --git a/include/Page.hpp b/include/Page.hpp
index d0e40b3..4cf1821 100644
--- a/include/Page.hpp
+++ b/include/Page.hpp
@@ -9,6 +9,7 @@ namespace QuickMedia {
IMAGE_BOARD_THREAD,
CHAT_LOGIN,
CHAT,
- FILE_MANAGER
+ FILE_MANAGER,
+ CHAT_INVITE
};
} \ No newline at end of file
diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp
index 2e25d69..293b00a 100644
--- a/plugins/Matrix.hpp
+++ b/plugins/Matrix.hpp
@@ -495,6 +495,19 @@ namespace QuickMedia {
MatrixRoomsPage *all_rooms_page;
};
+ class MatrixInviteUserPage : public Page {
+ public:
+ MatrixInviteUserPage(Program *program, Matrix *matrix, std::string room_id) : Page(program), matrix(matrix), room_id(std::move(room_id)) {}
+ const char* get_title() const override { return "Invite user"; }
+ bool search_is_filter() override { return false; }
+ SearchResult search(const std::string &str, BodyItems &result_items) override;
+ PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
+ bool allow_submit_no_selection() const override { return true; }
+ private:
+ Matrix *matrix;
+ std::string room_id;
+ };
+
class Matrix {
public:
// TODO: Make this return the Matrix object instead, to force users to call start_sync
@@ -537,12 +550,15 @@ namespace QuickMedia {
PluginResult set_read_marker(RoomData *room, const std::string &event_id, int64_t event_timestamp);
- PluginResult join_room(const std::string &room_id);
+ PluginResult join_room(const std::string &room_id_or_name);
PluginResult leave_room(const std::string &room_id);
// If |since| is empty, then the first page is fetched
PluginResult get_public_rooms(const std::string &server, const std::string &search_term, const std::string &since, BodyItems &rooms, std::string &next_batch);
+ PluginResult search_user(const std::string &search_term, unsigned int limit, BodyItems &result_items);
+ PluginResult invite_user(const std::string &room_id, const std::string &user_id);
+
// |message| is from |BodyItem.userdata| and is of type |Message*|
bool was_message_posted_by_me(void *message);
diff --git a/src/Body.cpp b/src/Body.cpp
index 6c2cf9e..693086e 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -1551,7 +1551,7 @@ namespace QuickMedia {
item_height += (get_item_height(item->embedded_item.get(), embedded_item_width, load_texture, false) + 6.0f + body_spacing[body_theme].embedded_item_padding_y * 2.0f);
else
item_height += ((body_spacing[body_theme].embedded_item_font_size + 5.0f) + 6.0f + body_spacing[body_theme].embedded_item_padding_y * 2.0f);
- has_loaded_text = true;
+ has_loaded_text = true; // TODO: Remove this
}
if(item->description_text) {
item_height += item->description_text->getHeight() - 2.0f;
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 07d6640..f8566fa 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -5045,6 +5045,29 @@ namespace QuickMedia {
chat_input.set_editable(false);
chat_state = ChatState::NAVIGATING;
return true;
+ } else if(strncmp(text.c_str(), "/join ", 6) == 0) {
+ text.erase(text.begin(), text.begin() + 6);
+ text = strip(text);
+ if(text.empty()) {
+ return false;
+ } else {
+ TaskResult task_result = run_task_with_loading_screen([this, text{std::move(text)}] {
+ return matrix->join_room(text) == PluginResult::OK;
+ });
+
+ if(task_result == TaskResult::TRUE) {
+ chat_input.set_editable(false);
+ chat_state = ChatState::NAVIGATING;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ } else if(text == "/invite") {
+ new_page = PageType::CHAT_INVITE;
+ chat_input.set_editable(false);
+ chat_state = ChatState::NAVIGATING;
+ return true;
} else if(text == "/logout") {
new_page = PageType::CHAT_LOGIN;
chat_input.set_editable(false);
@@ -5067,7 +5090,7 @@ namespace QuickMedia {
msgtype = "m.reaction";
text.erase(text.begin(), text.begin() + 7);
} else {
- show_notification("QuickMedia", "Error: invalid command: " + text + ", expected /upload, /logout, /me or /react", Urgency::NORMAL);
+ show_notification("QuickMedia", "Error: invalid command: " + text + ", expected /upload, /join [room], /invite, /logout, /me [text] or /react [text]", Urgency::NORMAL);
return false;
}
} else if(chat_state == ChatState::REPLYING && text[0] == '/') {
@@ -5968,6 +5991,21 @@ namespace QuickMedia {
exit(exit_code);
break;
}
+ case PageType::CHAT_INVITE: {
+ new_page = PageType::CHAT;
+
+ for(ChatTab &tab : tabs) {
+ tab.body->clear_cache();
+ }
+
+ std::vector<Tab> new_tabs;
+ new_tabs.push_back(Tab{create_body(), std::make_unique<MatrixInviteUserPage>(this, matrix, current_room->id), create_search_bar("Search...", 350)});
+ page_loop(new_tabs);
+
+ redraw = true;
+ avatar_applied = false;
+ break;
+ }
default:
break;
}
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 4d00949..b0ff9b8 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -1128,6 +1128,17 @@ namespace QuickMedia {
}
}
+ SearchResult MatrixInviteUserPage::search(const std::string &str, BodyItems &result_items) {
+ return plugin_result_to_search_result(matrix->search_user(str, 20, result_items));
+ }
+
+ PluginResult MatrixInviteUserPage::submit(const std::string&, const std::string &url, std::vector<Tab>&) {
+ PluginResult result = matrix->invite_user(room_id, url);
+ if(result != PluginResult::OK) return result;
+ program->set_go_to_previous_page();
+ return PluginResult::OK;
+ }
+
static std::array<const char*, 7> sync_fail_error_codes = {
"M_FORBIDDEN",
"M_UNKNOWN_TOKEN",
@@ -3850,7 +3861,7 @@ namespace QuickMedia {
return download_result_to_plugin_result(download_result);
}
- PluginResult Matrix::join_room(const std::string &room_id) {
+ PluginResult Matrix::join_room(const std::string &room_id_or_name) {
assert(delegate);
std::vector<CommandArg> additional_args = {
{ "-X", "POST" },
@@ -3859,34 +3870,50 @@ namespace QuickMedia {
{ "-H", "Authorization: Bearer " + access_token }
};
- std::string server_response;
- DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/join/" + url_param_encode(room_id), server_response, std::move(additional_args), true);
- if(download_result == DownloadResult::OK) {
- std::lock_guard<std::mutex> invite_lock(invite_mutex);
- auto invite_it = invites.find(room_id);
- if(invite_it != invites.end()) {
- std::lock_guard<std::recursive_mutex> lock(room_data_mutex);
- RoomData *room = get_room_by_id(room_id);
- if(!room) {
- auto new_room = std::make_unique<RoomData>();
- new_room->id = room_id;
- new_room->set_name(invite_it->second.room_name);
- new_room->set_avatar_url(invite_it->second.room_avatar_url);
- room = new_room.get();
- add_room(std::move(new_room));
+ rapidjson::Document json_root;
+ std::string err_msg;
+ DownloadResult download_result = download_json(json_root, homeserver + "/_matrix/client/r0/join/" + url_param_encode(room_id_or_name), std::move(additional_args), true, &err_msg);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
- ui_thread_tasks.push([this, room]{ delegate->join_room(room); });
- room->acquire_room_lock();
- std::set<std::string> &room_tags = room->get_tags_unsafe();
- if(room_tags.empty()) {
- room_tags.insert(OTHERS_ROOM_TAG);
- ui_thread_tasks.push([this, room]{ delegate->room_add_tag(room, OTHERS_ROOM_TAG); });
- }
- room->release_room_lock();
+ if(!json_root.IsObject())
+ return PluginResult::ERR;
+
+ const rapidjson::Value &error_json = GetMember(json_root, "error");
+ if(error_json.IsString()) {
+ show_notification("QuickMedia", "Failed to join " + room_id_or_name + ", error: " + std::string(error_json.GetString(), error_json.GetStringLength()), Urgency::CRITICAL);
+ return PluginResult::ERR;
+ }
+
+ const rapidjson::Value &room_id_json = GetMember(json_root, "room_id");
+ if(!room_id_json.IsString())
+ return PluginResult::ERR;
+
+ const std::string room_id(room_id_json.GetString(), room_id_json.GetStringLength());
+
+ std::lock_guard<std::mutex> invite_lock(invite_mutex);
+ auto invite_it = invites.find(room_id);
+ if(invite_it != invites.end()) {
+ std::lock_guard<std::recursive_mutex> lock(room_data_mutex);
+ RoomData *room = get_room_by_id(room_id);
+ if(!room) {
+ auto new_room = std::make_unique<RoomData>();
+ new_room->id = room_id;
+ new_room->set_name(invite_it->second.room_name);
+ new_room->set_avatar_url(invite_it->second.room_avatar_url);
+ room = new_room.get();
+ add_room(std::move(new_room));
+
+ ui_thread_tasks.push([this, room]{ delegate->join_room(room); });
+ room->acquire_room_lock();
+ std::set<std::string> &room_tags = room->get_tags_unsafe();
+ if(room_tags.empty()) {
+ room_tags.insert(OTHERS_ROOM_TAG);
+ ui_thread_tasks.push([this, room]{ delegate->room_add_tag(room, OTHERS_ROOM_TAG); });
}
+ room->release_room_lock();
}
}
- return download_result_to_plugin_result(download_result);
+ return PluginResult::OK;
}
PluginResult Matrix::leave_room(const std::string &room_id) {
@@ -4012,6 +4039,107 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ PluginResult Matrix::search_user(const std::string &search_term, unsigned int limit, BodyItems &result_items) {
+ rapidjson::Document request_data(rapidjson::kObjectType);
+ request_data.AddMember("search_term", rapidjson::StringRef(search_term.c_str()), request_data.GetAllocator());
+ request_data.AddMember("limit", limit, request_data.GetAllocator());
+
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+ request_data.Accept(writer);
+
+ std::vector<CommandArg> additional_args = {
+ { "-X", "POST" },
+ { "-H", "content-type: application/json" },
+ { "--data-binary", buffer.GetString() },
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ rapidjson::Document json_root;
+ std::string err_msg;
+ DownloadResult download_result = download_json(json_root, homeserver + "/_matrix/client/r0/user_directory/search", std::move(additional_args), true, &err_msg);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
+
+ if(!json_root.IsObject())
+ return PluginResult::ERR;
+
+ const rapidjson::Value &error_json = GetMember(json_root, "error");
+ if(error_json.IsString()) {
+ show_notification("QuickMedia", "Failed to search for " + search_term + ", error: " + std::string(error_json.GetString(), error_json.GetStringLength()), Urgency::CRITICAL);
+ return PluginResult::ERR;
+ }
+
+ const rapidjson::Value &results_json = GetMember(json_root, "results");
+ if(!results_json.IsArray())
+ return PluginResult::OK;
+
+ for(const rapidjson::Value &result_item_json : results_json.GetArray()) {
+ if(!result_item_json.IsObject())
+ continue;
+
+ const rapidjson::Value &user_id_json = GetMember(result_item_json, "user_id");
+ const rapidjson::Value &display_name_json = GetMember(result_item_json, "display_name");
+ const rapidjson::Value &avatar_url_json = GetMember(result_item_json, "avatar_url");
+ if(!user_id_json.IsString())
+ continue;
+
+ auto body_item = BodyItem::create("");
+ body_item->url.assign(user_id_json.GetString(), user_id_json.GetStringLength());
+ body_item->set_description(body_item->url);
+ body_item->set_description_color(get_current_theme().faded_text_color);
+ if(display_name_json.IsString())
+ body_item->set_author(std::string(display_name_json.GetString(), display_name_json.GetStringLength()));
+ else
+ body_item->set_author(std::string(user_id_json.GetString(), user_id_json.GetStringLength()));
+ body_item->set_author_color(user_id_to_color(body_item->url));
+
+ if(avatar_url_json.IsString()) {
+ std::string avatar_url = thumbnail_url_extract_media_id(std::string(avatar_url_json.GetString(), avatar_url_json.GetStringLength()));
+ if(!avatar_url.empty())
+ avatar_url = get_thumbnail_url(homeserver, avatar_url);
+ body_item->thumbnail_url = std::move(avatar_url);
+ }
+ body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ body_item->thumbnail_size = sf::Vector2i(32, 32);
+
+ result_items.push_back(std::move(body_item));
+ }
+
+ return PluginResult::OK;
+ }
+
+ PluginResult Matrix::invite_user(const std::string &room_id, const std::string &user_id) {
+ rapidjson::Document request_data(rapidjson::kObjectType);
+ request_data.AddMember("user_id", rapidjson::StringRef(user_id.c_str()), request_data.GetAllocator());
+
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+ request_data.Accept(writer);
+
+ std::vector<CommandArg> additional_args = {
+ { "-X", "POST" },
+ { "-H", "content-type: application/json" },
+ { "--data-binary", buffer.GetString() },
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ rapidjson::Document json_root;
+ std::string err_msg;
+ DownloadResult download_result = download_json(json_root, homeserver + "/_matrix/client/r0/rooms/" + room_id + "/invite", std::move(additional_args), true, &err_msg);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
+
+ if(!json_root.IsObject())
+ return PluginResult::ERR;
+
+ const rapidjson::Value &error_json = GetMember(json_root, "error");
+ if(error_json.IsString()) {
+ show_notification("QuickMedia", "Failed to invite " + user_id + " to " + room_id + ", error: " + std::string(error_json.GetString(), error_json.GetStringLength()), Urgency::CRITICAL);
+ return PluginResult::ERR;
+ }
+
+ return PluginResult::OK;
+ }
+
bool Matrix::was_message_posted_by_me(void *message) {
Message *message_typed = (Message*)message;
return my_user_id == message_typed->user->user_id;