From 818f65e1d9e21a2b0dcecf34312b217000da7c92 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 22 Nov 2020 11:46:01 +0100 Subject: Matrix: add /react --- README.md | 3 ++- TODO | 2 ++ plugins/Matrix.hpp | 2 ++ src/QuickMedia.cpp | 54 +++++++++++++++++++++++++++++++++++--------------- src/plugins/Matrix.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 2e49c7a..b2aacc7 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,8 @@ In matrix you can select a message with enter to open the url in the message (or `/upload` to upload an image.\ `/logout` to logout.\ `/leave` to leave the current room.\ -`/me` to send a message of type "m.emote". +`/me [text]` to send a message of type "m.emote".\ +`/react [text]` to react to the selected message. # Mangadex To search for manga with mangadex, you need to be logged into mangadex in your browser and copy the `mangadex_rememberme_token` cookie from developer tools and store it in `$HOME/.config/quickmedia/credentials/mangadex.json` under the key `rememberme_token`. Here is an example what the file should look like: diff --git a/TODO b/TODO index 45a0f49..999f0ce 100644 --- a/TODO +++ b/TODO @@ -140,3 +140,5 @@ Have a list of redacted events so when fetching previous events, we can filter o Add grid view to matrix and navigate between them using alt+arrow keys. Show reactions in pinned messages tab? Remove display names from reactions if there are many reactions, and instead group them into: reaction (#number of this type of reaction); for example: 👍 2. +Make reaction and deleted message provisional. +Allow removing reactions. \ No newline at end of file diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index 1a0ffe0..a806d98 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -451,6 +451,8 @@ namespace QuickMedia { PluginResult post_reply(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response); // |relates_to| is from |BodyItem.userdata| and is of type |Message*| PluginResult post_edit(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response); + // |relates_to| is from |BodyItem.userdata| and is of type |Message*| + PluginResult post_reaction(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response); PluginResult post_file(RoomData *room, const std::string &filepath, std::string &event_id_response, std::string &err_msg); PluginResult login(const std::string &username, const std::string &password, const std::string &homeserver, std::string &err_msg); diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index d9b1041..0f4c695 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3433,7 +3433,7 @@ namespace QuickMedia { auto filter_my_messages = [&me](Messages &messages) { for(auto it = messages.begin(); it != messages.end();) { - if((*it)->user == me && (*it)->type == MessageType::TEXT) + if((*it)->user == me && ((*it)->type == MessageType::TEXT || (*it)->type == MessageType::REACTION)) it = messages.erase(it); else ++it; @@ -3467,7 +3467,7 @@ namespace QuickMedia { } }; - chat_input.on_submit_callback = [this, &tabs, &me, &chat_input, &selected_tab, ¤t_room, &new_page, &chat_state, ¤tly_operating_on_item, &post_task_queue, &unreferenced_events, &find_body_item_by_event_id](std::string text) mutable { + chat_input.on_submit_callback = [this, &tabs, &me, &chat_input, &selected_tab, ¤t_room, &new_page, &chat_state, ¤tly_operating_on_item, &post_task_queue, &process_reactions, &find_body_item_by_event_id](std::string text) mutable { if(!current_room) return false; @@ -3500,8 +3500,11 @@ namespace QuickMedia { } else if(strncmp(text.c_str(), "/me ", 4) == 0) { msgtype = "m.emote"; text.erase(text.begin(), text.begin() + 4); + } else if(strncmp(text.c_str(), "/react ", 7) == 0) { + msgtype = "m.reaction"; + text.erase(text.begin(), text.begin() + 7); } else { - fprintf(stderr, "Error: invalid command: %s, expected /upload, /logout or /me\n", text.c_str()); + fprintf(stderr, "Error: invalid command: %s, expected /upload, /logout, /me or /react\n", text.c_str()); return false; } } @@ -3523,17 +3526,38 @@ namespace QuickMedia { scroll_to_end = true; if(chat_state == ChatState::TYPING_MESSAGE) { - auto body_item = message_to_body_item(current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); - body_item->set_description_color(provisional_message_color); - tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps({body_item}); - post_task_queue.push([this, ¤t_room, text, msgtype, body_item, message]() { - ProvisionalMessage provisional_message; - provisional_message.body_item = body_item; - provisional_message.message = message; - if(matrix->post_message(current_room, text, provisional_message.event_id, std::nullopt, std::nullopt, msgtype) != PluginResult::OK) - fprintf(stderr, "Failed to post matrix message\n"); - return provisional_message; - }); + BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected(); + if(msgtype == "m.reaction" && selected_item) { + void *related_to_message = selected_item->userdata; + message->type = MessageType::REACTION; + message->related_event_type = RelatedEventType::REACTION; + message->related_event_id = static_cast(related_to_message)->event_id; + auto body_item = message_to_body_item(current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); + tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps({body_item}); + Messages messages; + messages.push_back(message); + process_reactions(messages); + post_task_queue.push([this, ¤t_room, text, body_item, message, related_to_message]() { + ProvisionalMessage provisional_message; + provisional_message.body_item = body_item; + provisional_message.message = message; + if(matrix->post_reaction(current_room, text, related_to_message, provisional_message.event_id) != PluginResult::OK) + fprintf(stderr, "Failed to post matrix reaction\n"); + return provisional_message; + }); + } else { + auto body_item = message_to_body_item(current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); + body_item->set_description_color(provisional_message_color); + tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps({body_item}); + post_task_queue.push([this, ¤t_room, text, msgtype, body_item, message]() { + ProvisionalMessage provisional_message; + provisional_message.body_item = body_item; + provisional_message.message = message; + if(matrix->post_message(current_room, text, provisional_message.event_id, std::nullopt, std::nullopt, msgtype) != PluginResult::OK) + fprintf(stderr, "Failed to post matrix message\n"); + return provisional_message; + }); + } chat_input.set_editable(false); chat_state = ChatState::NAVIGATING; if(scroll_to_end) @@ -4107,8 +4131,6 @@ namespace QuickMedia { if((selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX) && event.key.code == sf::Keyboard::Enter) { BodyItem *selected = tabs[selected_tab].body->get_selected(); if(selected) { - Message *mess = static_cast(selected->userdata); - fprintf(stderr, "Mess type: %d\n", mess->type); if(!display_url_or_image(selected)) display_url_or_image(selected->embedded_item.get()); } diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index e5e4922..05881d5 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -2733,6 +2733,53 @@ namespace QuickMedia { return PluginResult::OK; } + PluginResult Matrix::post_reaction(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response) { + Message *relates_to_message_raw = (Message*)relates_to; + + 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)); + + rapidjson::Document relates_to_json(rapidjson::kObjectType); + relates_to_json.AddMember("event_id", rapidjson::StringRef(relates_to_message_raw->event_id.c_str()), relates_to_json.GetAllocator()); + relates_to_json.AddMember("key", rapidjson::StringRef(body.c_str()), relates_to_json.GetAllocator()); + relates_to_json.AddMember("rel_type", "m.annotation", relates_to_json.GetAllocator()); + + rapidjson::Document request_json(rapidjson::kObjectType); + request_json.AddMember("m.relates_to", std::move(relates_to_json), request_json.GetAllocator()); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_json.Accept(writer); + + std::vector additional_args = { + { "-X", "PUT" }, + { "-H", "content-type: application/json" }, + { "-H", "Authorization: Bearer " + access_token }, + { "--data-binary", buffer.GetString() } + }; + + char request_url[512]; + snprintf(request_url, sizeof(request_url), "%s/_matrix/client/r0/rooms/%s/send/m.reaction/m%ld.%.*s", homeserver.c_str(), room->id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str()); + + rapidjson::Document json_root; + DownloadResult download_result = download_json(json_root, request_url, std::move(additional_args), true); + if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); + + if(!json_root.IsObject()) + return PluginResult::ERR; + + const rapidjson::Value &event_id_json = GetMember(json_root, "event_id"); + if(!event_id_json.IsString()) + return PluginResult::ERR; + + fprintf(stderr, "Matrix post reaction, response event id: %s\n", event_id_json.GetString()); + event_id_response = std::string(event_id_json.GetString(), event_id_json.GetStringLength()); + return PluginResult::OK; + } + std::shared_ptr Matrix::get_message_by_id(RoomData *room, const std::string &event_id) { std::shared_ptr existing_room_message = room->get_message_by_id(event_id); if(existing_room_message) -- cgit v1.2.3