#pragma once #include "../include/FileAnalyzer.hpp" #include "../include/MessageQueue.hpp" #include "Plugin.hpp" #include "Page.hpp" #include #include #include #include #include #include #include #include #include namespace QuickMedia { struct RoomData; struct Message; static const int AUTHOR_MAX_LENGTH = 48; class Matrix; std::string extract_first_line_remove_newline_elipses(const std::string &str, size_t max_length); mgl::Color user_id_to_color(const std::string &user_id); // |image_max_size| 0, 0 means no max size std::string formatted_text_to_qm_text(Matrix *matrix, const char *str, size_t size, bool allow_formatted_text, mgl::vec2i image_max_size = mgl::vec2i(0, 0)); // |image_max_size| 0, 0 means no max size std::string message_to_qm_text(Matrix *matrix, const Message *message, bool allow_formatted_text = true, mgl::vec2i image_max_size = mgl::vec2i(0, 0)); std::string pantalaimon_url_to_homeserver_url(Matrix *matrix, const std::string &url); Message* get_latest_message_in_edit_chain(Message *message); bool matrix_gpg_encrypt_for_each_user_in_room(Matrix *matrix, RoomData *room, const std::string &my_gpg_user_id, const std::string &str, std::string &encrypted_str); // Returns empty string on error std::string extract_user_name_from_user_id(const std::string &user_id); // Returns empty string on error std::string extract_user_name_from_email(const std::string &email); std::vector matrix_extract_room_ids(const std::string &str); std::string remove_reply_formatting(Matrix *matrix, const Message *message, bool keep_formatted = false); struct MatrixChatBodyDecryptJob { enum class DecryptState { NOT_DECRYPTED, DECRYPTING, DECRYPTED, FAILED_TO_DECRYPT }; std::string text; DecryptState decrypt_state = DecryptState::NOT_DECRYPTED; bool cancel = false; }; class MatrixChatBodyItemData : public BodyItemExtra { public: enum class DecryptState { NOT_DECRYPTED, DECRYPTING, DECRYPTED }; MatrixChatBodyItemData(Matrix *matrix, std::string text_to_decrypt, RoomData *room = nullptr, uint64_t gpg_decrypt_message_id = 0) : matrix(matrix), text_to_decrypt(std::move(text_to_decrypt)), room(room), gpg_decrypt_message_id(gpg_decrypt_message_id) {} ~MatrixChatBodyItemData(); void draw_overlay(mgl::Window&, const Widgets &widgets) override; DecryptState decrypt_state = DecryptState::NOT_DECRYPTED; std::shared_ptr decrypt_job; Matrix *matrix = nullptr; std::string text_to_decrypt; RoomData *room = nullptr; uint64_t gpg_decrypt_message_id = 0; }; // TODO: Remove. Not needed anymore. struct TimestampedDisplayData { std::string data; time_t timestamp = 0; // In milliseconds // Force update by settings |new_timestamp| to 0 bool set_data_if_newer(std::string new_data, time_t new_timestamp); }; struct UserInfo { friend struct RoomData; UserInfo(RoomData *room, std::string user_id); UserInfo(RoomData *room, std::string user_id, std::string display_name, std::string avatar_url, time_t update_timestamp_ms); RoomData *room; const mgl::Color display_name_color; const std::string user_id; int power_level = 0; private: TimestampedDisplayData display_name; TimestampedDisplayData avatar_url; std::string read_marker_event_id; }; enum class MessageType { TEXT, IMAGE, VIDEO, AUDIO, FILE, REDACTION, REACTION, MEMBERSHIP, SYSTEM, UNIMPLEMENTED }; bool is_visual_media_message_type(MessageType message_type); bool is_system_message_type(MessageType message_type); enum class RelatedEventType { NONE, REPLY, EDIT, REDACTION, REACTION }; struct MatrixEventUserInfo { RoomData *room; std::string user_id; std::optional display_name; std::optional avatar_url; }; struct MatrixEventRoomInfo { RoomData *room; std::optional name; std::optional topic; std::optional avatar_url; }; enum class MatrixEventType { ADD_USER, REMOVE_USER, USER_INFO, ROOM_INFO }; struct Message { std::shared_ptr user; std::string event_id; std::string body; std::string url; std::string thumbnail_url; std::string related_event_id; mgl::vec2i thumbnail_size; // Set to {0, 0} if not specified RelatedEventType related_event_type = RelatedEventType::NONE; bool notification_mentions_me = false; bool body_is_formatted = false; std::string transaction_id; time_t timestamp = 0; // In milliseconds MessageType type; Message *replaces = nullptr; std::shared_ptr replaced_by = nullptr; // TODO: Store body item ref here }; using Messages = std::vector>; struct RoomData { std::shared_ptr get_user_by_id(const std::string &user_id); void add_user(std::shared_ptr user); void set_user_read_marker(std::shared_ptr &user, const std::string &event_id); std::string get_user_read_marker(const std::shared_ptr &user); std::string get_user_display_name(const std::shared_ptr &user); std::string get_user_avatar_url(const std::shared_ptr &user); // Set to empty to remove (in which case the display name will be the user id) bool set_user_display_name(std::shared_ptr &user, std::string display_name, time_t update_timestamp_ms); bool set_user_avatar_url(std::shared_ptr &user, std::string avatar_url, time_t update_timestamp_ms); // Ignores duplicates, returns the number of added messages size_t prepend_messages_reverse(const Messages &new_messages); // Ignores duplicates, returns the number of added messages size_t append_messages(const Messages &new_messages); std::shared_ptr get_message_by_id(const std::string &id); std::vector> get_users(); std::vector> get_users_excluding_me(const std::string &my_user_id); void acquire_room_lock(); void release_room_lock(); const Messages& get_messages_thread_unsafe() const; const std::vector& get_pinned_events_thread_unsafe() const; void clear_messages(); bool has_prev_batch(); void set_prev_batch(const std::string &new_prev_batch); std::string get_prev_batch(); bool has_name(); bool set_name(const std::string &new_name, time_t update_timestamp_ms); // TODO: Remove this std::string get_name(); bool set_topic(const std::string &new_topic, time_t update_timestamp_ms); std::string get_topic(); bool has_avatar_url(); bool set_avatar_url(const std::string &new_avatar_url, time_t update_timestamp_ms); std::string get_avatar_url(); void set_pinned_events(std::vector new_pinned_events); std::set& get_tags_thread_unsafe(); void clear_data(); std::string id; // These 5 variables are set by QuickMedia, not the matrix plugin bool initial_prev_messages_fetch = true; bool last_message_read = true; bool users_fetched = false; time_t last_read_message_timestamp = 0; std::shared_ptr body_item; int offset_to_latest_message_text = 0; // These are messages fetched with |Matrix::get_message_by_id|. Needed to show replies, when replying to old message not part of /sync. // The value is nullptr if the message is fetched and cached but the event if referenced an invalid message. // TODO: Verify if replied to messages are also part of /sync; then this is not needed. std::unordered_map> fetched_messages_by_event_id; size_t messages_read_index = 0; bool pinned_events_updated = false; bool name_is_fallback = false; bool avatar_is_fallback = false; std::atomic_int64_t last_message_timestamp = 0; std::atomic_int64_t read_marker_event_timestamp = 0; int notification_power_level = 50; size_t index = 0; std::atomic gpg_decrypt_message_id = 0; std::string chat_message; private: std::mutex user_mutex; std::recursive_mutex room_mutex; TimestampedDisplayData name; TimestampedDisplayData topic; TimestampedDisplayData avatar_url; std::string prev_batch; // Each room has its own list of user data, even if multiple rooms has the same user // because users can have different display names and avatars in different rooms. std::unordered_map> user_info_by_user_id; Messages messages; std::unordered_map> message_by_event_id; std::vector pinned_events; std::set tags; }; struct Invite { std::string room_name; std::string room_avatar_url; std::shared_ptr invited_by; time_t timestamp = 0; // In milliseconds }; enum class MessageDirection { BEFORE, AFTER }; struct UploadInfo { ContentType content_type; int64_t file_size = 0; std::optional dimensions; std::optional duration_seconds; std::string content_uri; }; struct SyncData { Messages messages; std::optional> pinned_events; }; using Rooms = std::vector; bool message_contains_user_mention(Matrix *matrix, const Message *message, const std::string &username, const std::string &user_id); bool message_contains_user_mention(const BodyItem *body_item, const std::string &username, const std::string &user_id); bool message_is_timeline(Message *message); void body_set_selected_item_by_url(Body *body, const std::string &url); std::string create_transaction_id(); enum class MatrixPageType { ROOM_LIST, CHAT }; enum class LeaveType { LEAVE, KICKED, BANNED }; struct MatrixNotification { RoomData *room; std::string event_id; std::string sender_user_id; std::string body; // Without reply formatting time_t timestamp; // The timestamp in milliseconds or 0 bool read; }; // All of methods in this class are called in the main (ui) thread class MatrixDelegate { public: virtual ~MatrixDelegate() = default; virtual void join_room(RoomData *room) = 0; virtual void leave_room(RoomData *room, LeaveType leave_type, const std::string &reason) = 0; // Note: calling |room| methods inside this function is not allowed virtual void room_add_tag(RoomData *room, const std::string &tag) = 0; // Note: calling |room| methods inside this function is not allowed virtual void room_remove_tag(RoomData *room, const std::string &tag) = 0; virtual void room_add_new_messages(RoomData *room, const Messages &messages, bool is_initial_sync, MessageDirection message_dir) = 0; virtual void room_clear_messages(RoomData *room) = 0; virtual void add_invite(const std::string &room_id, const Invite &invite) = 0; virtual void remove_invite(const std::string &room_id) = 0; virtual void add_unread_notification(MatrixNotification notification) = 0; virtual void add_user(MatrixEventUserInfo user_info) = 0; virtual void remove_user(MatrixEventUserInfo user_info) = 0; virtual void set_user_info(MatrixEventUserInfo user_info) = 0; virtual void set_room_info(MatrixEventRoomInfo room_info) = 0; virtual void set_room_as_read(RoomData *room) = 0; }; class Matrix; class MatrixRoomsPage; class MatrixRoomTagsPage; class MatrixInvitesPage; class MatrixChatPage; class MatrixNotificationsPage; using UsersByRoom = std::unordered_map>; class MatrixQuickMedia : public MatrixDelegate { public: MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page, MatrixInvitesPage *invites_page, MatrixNotificationsPage *notifications_page); void join_room(RoomData *room) override; void leave_room(RoomData *room, LeaveType leave_type, const std::string &reason) override; void room_add_tag(RoomData *room, const std::string &tag) override; void room_remove_tag(RoomData *room, const std::string &tag) override; void room_add_new_messages(RoomData *room, const Messages &messages, bool is_initial_sync, MessageDirection message_dir) override; void room_clear_messages(RoomData *room) override; void add_invite(const std::string &room_id, const Invite &invite) override; void remove_invite(const std::string &room_id) override; void add_unread_notification(MatrixNotification notification) override; void add_user(MatrixEventUserInfo user_info) override; void remove_user(MatrixEventUserInfo user_info) override; void set_user_info(MatrixEventUserInfo user_info) override; void set_room_info(MatrixEventRoomInfo room_info) override; void for_each_user_in_room(RoomData *room, std::function callback); void set_room_as_read(RoomData *room) override; Program *program; Matrix *matrix; MatrixChatPage *chat_page; MatrixRoomsPage *rooms_page; MatrixRoomTagsPage *room_tags_page; MatrixInvitesPage *invites_page; MatrixNotificationsPage *notifications_page; private: void update_room_description(RoomData *room, const Messages &new_messages, bool is_initial_sync); private: std::map> room_body_item_by_room; std::map> last_message_by_room; std::map unread_mention_count_by_room; std::unordered_set notifications_shown; UsersByRoom users_by_room; }; class MatrixRoomsPage : public Page { public: MatrixRoomsPage(Program *program, Body *body, std::string title, MatrixRoomTagsPage *room_tags_page, SearchBar *search_bar); ~MatrixRoomsPage() override; const char* get_title() const override { return title.c_str(); } PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; bool submit_is_async() const override { return false; } bool clear_search_after_submit() override { return true; } void add_body_item(std::shared_ptr body_item); void move_room_to_top(RoomData *room); void remove_body_item_by_room_id(const std::string &room_id); void set_current_chat_page(MatrixChatPage *chat_page); void set_room_as_read(RoomData *room); void clear_search(); MatrixQuickMedia *matrix_delegate = nullptr; Body *body = nullptr; private: std::string title; MatrixRoomTagsPage *room_tags_page = nullptr; MatrixChatPage *current_chat_page = nullptr; SearchBar *search_bar; }; class MatrixRoomTagsPage : public Page { public: MatrixRoomTagsPage(Program *program, Body *body) : Page(program), body(body) {} const char* get_title() const override { return "Tags"; } PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; bool submit_is_async() const override { return false; } bool clear_search_after_submit() override { return true; } void add_room_body_item_to_tag(std::shared_ptr body_item, const std::string &tag); void remove_room_body_item_from_tag(std::shared_ptr body_item, const std::string &tag); void move_room_to_top(RoomData *room); void remove_body_item_by_room_id(const std::string &room_id); void set_current_rooms_page(MatrixRoomsPage *rooms_page); MatrixQuickMedia *matrix_delegate = nullptr; private: struct TagData { std::shared_ptr tag_item; std::vector> room_body_items; }; Body *body; std::map tag_body_items_by_name; MatrixRoomsPage *current_rooms_page = nullptr; }; class MatrixInvitesPage : public Page { public: MatrixInvitesPage(Program *program, Matrix *matrix, Body *body); const char* get_title() const override { return title.c_str(); } PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; bool submit_is_async() const override { return false; } bool clear_search_after_submit() override { return true; } void add_body_item(std::shared_ptr body_item); void remove_body_item_by_room_id(const std::string &room_id); private: Matrix *matrix; Body *body; std::string title = "Invites (0)"; size_t prev_invite_count = 0; }; class MatrixInviteDetailsPage : public Page { public: MatrixInviteDetailsPage(Program *program, Matrix *matrix, MatrixInvitesPage *invites_page, std::string room_id, std::string title) : Page(program), matrix(matrix), invites_page(invites_page), room_id(std::move(room_id)), title(std::move(title)) {} const char* get_title() const override { return title.c_str(); } PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; Matrix *matrix; MatrixInvitesPage *invites_page; const std::string room_id; const std::string title; }; class MatrixSettingsPage : public Page { public: MatrixSettingsPage(Program *program, Matrix *matrix) : Page(program), matrix(matrix) {} const char* get_title() const override { return "Settings"; } PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; private: Matrix *matrix; }; class MatrixRoomInputPage : public Page { public: MatrixRoomInputPage(Program *program, Matrix *matrix) : Page(program), matrix(matrix) {} const char* get_title() const override { return "Enter the id of a room to join"; } PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; bool allow_submit_no_selection() const override { return true; } private: Matrix *matrix; }; class MatrixCustomEmojiPage : public LazyFetchPage { public: MatrixCustomEmojiPage(Program *program, Matrix *matrix) : LazyFetchPage(program), matrix(matrix) {} const char* get_title() const override { return "Custom emoji"; } PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; PluginResult lazy_fetch(BodyItems &result_items) override; bool is_ready() override; private: Matrix *matrix; }; class MatrixCustomEmojiRenameSelectPage : public LazyFetchPage { public: MatrixCustomEmojiRenameSelectPage(Program *program, Matrix *matrix) : LazyFetchPage(program), matrix(matrix) {} const char* get_title() const override { return "Select emoji to rename"; } PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; PluginResult lazy_fetch(BodyItems &result_items) override; bool submit_is_async() const override { return false; } bool reload_on_page_change() override { return true; } private: Matrix *matrix; }; class MatrixCustomEmojiRenamePage : public Page { public: MatrixCustomEmojiRenamePage(Program *program, Matrix *matrix, std::string emoji_key) : Page(program), matrix(matrix), emoji_key(std::move(emoji_key)) {} const char* get_title() const override { return "Enter a new name for the emoji"; } PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; bool allow_submit_no_selection() const override { return true; } private: Matrix *matrix; std::string emoji_key; }; class MatrixCustomEmojiDeletePage : public Page { public: MatrixCustomEmojiDeletePage(Program *program, Matrix *matrix, Body *body) : Page(program), matrix(matrix), body(body) {} const char* get_title() const override { return "Select emoji to delete"; } PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; private: Matrix *matrix; Body *body; }; // Only play one video. TODO: Play all videos in room, as related videos? class MatrixVideoPage : public VideoPage { public: MatrixVideoPage(Program *program, std::string filename) : VideoPage(program, ""), filename(std::move(filename)) {} const char* get_title() const override { return ""; } std::string get_filename() override { return filename; } private: std::string filename; }; using MatrixRoomInfoUpdateCallback = std::function; class MatrixChatPage : public Page { public: MatrixChatPage(Program *program, std::string room_id, MatrixRoomsPage *rooms_page, std::string jump_to_event_id = ""); ~MatrixChatPage(); const char* get_title() const override { return ""; } PageTypez get_type() const override { return PageTypez::CHAT; } void add_user(MatrixEventUserInfo user_info); void remove_user(MatrixEventUserInfo user_info); void set_user_info(MatrixEventUserInfo user_info); void set_room_info(MatrixEventRoomInfo room_info); void set_current_room(RoomData *room, Body *users_body, MatrixRoomInfoUpdateCallback room_info_update_callback); size_t get_num_users_in_current_room() const; void set_room_as_read(RoomData *room); const std::string room_id; MatrixRoomsPage *rooms_page = nullptr; Body *chat_body = nullptr; bool messages_tab_visible = false; bool is_regular_navigation = true; const std::string jump_to_event_id; private: void add_user_to_body_by_user_info(const MatrixEventUserInfo &user_info); private: RoomData *current_room = nullptr; Body *users_body = nullptr; MatrixRoomInfoUpdateCallback room_info_update_callback; std::unordered_map user_body_item_by_user_id; }; class MatrixRoomDirectoryPage : public Page { public: MatrixRoomDirectoryPage(Program *program, Matrix *matrix) : Page(program), matrix(matrix) {} const char* get_title() const override { return "Room directory"; } bool allow_submit_no_selection() const override { return true; } bool clear_search_after_submit() override { return true; } PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; private: Matrix *matrix; }; class MatrixServerRoomListPage : public LazyFetchPage { public: MatrixServerRoomListPage(Program *program, Matrix *matrix, const std::string &server_name) : LazyFetchPage(program), matrix(matrix), server_name(server_name), current_page(0) {} const char* get_title() const override { return "Select a room to join"; } bool search_is_filter() override { return false; } PluginResult lazy_fetch(BodyItems &result_items) override; PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override; SearchResult search(const std::string &str, BodyItems &result_items) override; PluginResult submit(const SubmitArgs &args, std::vector &result_tabs) override; private: Matrix *matrix; const std::string server_name; std::string next_batch; std::string search_term; int current_page; }; class MatrixNotificationsPage : public LazyFetchPage { public: MatrixNotificationsPage(Program *program, Matrix *matrix, Body *notifications_body, MatrixRoomsPage *all_rooms_page); const char* get_title() const override { return "Notifications"; } PluginResult submit(const SubmitArgs &args, std::vector&) override; PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override; PluginResult lazy_fetch(BodyItems &result_items) override; bool is_ready() override; bool submit_is_async() const override { return false; } bool clear_search_after_submit() override { return true; } void add_notification(MatrixNotification notification); void set_room_as_read(RoomData *room); private: bool has_fetched = false; Matrix *matrix; Body *notifications_body; MatrixRoomsPage *all_rooms_page; // room id[event_id[]] std::unordered_map>> room_notifications; // Notifications are here until the notifications has been fetched, so that page handler doesn't the notifications std::unordered_map> pending_room_notifications; }; 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 SubmitArgs &args, std::vector &result_tabs) override; bool allow_submit_no_selection() const override { return true; } private: Matrix *matrix; std::string room_id; }; struct CustomEmoji { std::string url; mgl::vec2i size; }; class Matrix { public: Matrix(bool matrix_instance_already_running); // TODO: Make this return the Matrix object instead, to force users to call start_sync bool start_sync(MatrixDelegate *delegate, bool &cached); void stop_sync(); bool is_initial_sync_finished(); // Returns true if initial sync failed, and |err_msg| is set to the error reason in that case bool did_initial_sync_fail(std::string &err_msg); bool has_finished_fetching_notifications() const; void get_room_sync_data(RoomData *room, SyncData &sync_data); void get_all_synced_room_messages(RoomData *room, Messages &messages); void get_all_pinned_events(RoomData *room, std::vector &events); PluginResult get_messages_in_direction(RoomData *room, const std::string &token, MessageDirection message_dir, Messages &messages, std::string &new_token); PluginResult get_previous_room_messages(RoomData *room, Messages &messages, bool latest_messages = false, bool *reached_end = nullptr); PluginResult get_previous_notifications(std::function callback_func); void get_cached_notifications(std::function callback_func); // |url| should only be set when uploading media. // TODO: Make api better. PluginResult post_message(RoomData *room, const std::string &body, std::string &event_id_response, const std::optional &file_info, const std::optional &thumbnail_info, const std::string &msgtype = "", const std::string &custom_transaction_id = ""); // |relates_to| is from |BodyItem.userdata| and is of type |Message*| // If |custom_transaction_id| is empty, then a new transaction id is generated PluginResult post_reply(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response, const std::string &custom_transaction_id = "", const std::optional &file_info = std::nullopt, const std::optional &thumbnail_info = std::nullopt); // |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, const std::string &custom_transaction_id = ""); // |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, const std::string &custom_transaction_id = ""); // If filename is empty then the filename is extracted from filepath PluginResult post_file(RoomData *room, const std::string &filepath, std::string filename, std::string &event_id_response, std::string &err_msg, void *relates_to = nullptr); PluginResult upload_custom_emoji(const std::string &filepath, const std::string &key, std::string &mxc_url, std::string &err_msg); bool delete_custom_emoji(const std::string &key); bool rename_custom_emoji(const std::string &key, const std::string &new_key); bool does_custom_emoji_with_name_exist(const std::string &name); std::unordered_map get_custom_emojis(); PluginResult login(const std::string &username, const std::string &password, const std::string &homeserver, std::string &err_msg); PluginResult logout(); // |message| is from |BodyItem.userdata| and is of type |Message*| PluginResult delete_message(RoomData *room, void *message, std::string &err_msg); PluginResult pin_message(RoomData *room, const std::string &event_id); PluginResult unpin_message(RoomData *room, const std::string &event_id); PluginResult load_cached_session(); PluginResult on_start_typing(RoomData *room); PluginResult on_stop_typing(RoomData *room); PluginResult set_read_marker(RoomData *room, const std::string &event_id, int64_t event_timestamp); PluginResult join_room(const std::string &room_id_or_name); PluginResult leave_room(const std::string &room_id); bool is_invite_silenced(const std::string &room_id, int64_t timestamp); void silence_invite(const std::string &room_id, int64_t timestamp); // 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); std::string message_get_author_displayname(Message *message) const; // Cached PluginResult get_config(int64_t *upload_size); std::shared_ptr get_me(RoomData *room); const std::string& get_homeserver_domain() const; std::string get_remote_homeserver_url() const; // Returns nullptr if message cant be found. Note: cached std::shared_ptr get_message_by_id(RoomData *room, const std::string &event_id); PluginResult get_message_context(RoomData *room, const std::string &event_id, std::shared_ptr &message, Messages &before_messages, Messages &after_messages, std::string &before_token, std::string &after_token); void clear_previous_messages_token(RoomData *room); RoomData* get_room_by_id(const std::string &id); void update_room_users(RoomData *room); std::string get_media_url(const std::string &mxc_id); void append_system_message(RoomData *room_data, std::shared_ptr message); std::string body_to_formatted_body(RoomData *room, const std::string &body); void on_exit_room(RoomData *room); void async_decrypt_message(std::shared_ptr decrypt_job); MatrixDelegate* get_delegate(); // Calls the |MatrixDelegate| pending events. // Should be called from the main (ui) thread void update(); private: void trigger_event(RoomData *room, MatrixEventType type, MatrixEventUserInfo user_info); void trigger_event(RoomData *room, MatrixEventType type, MatrixEventRoomInfo room_info); void formatted_body_add_line(RoomData *room, std::string &formatted_body, const std::string &line_str, const std::unordered_map &custom_emojis); void replace_mentions(RoomData *room, std::string &text); std::string create_formatted_body_for_message_reply(RoomData *room, const Message *message, const std::string &body); std::string create_formatted_body_for_message_edit(RoomData *room, const Message *replied_to_message, const std::string &body); PluginResult set_pinned_events(RoomData *room, const std::vector &pinned_events, bool is_add); PluginResult set_qm_last_read_message_timestamp(RoomData *room, int64_t timestamp); bool load_qm_read_markers_from_account_data(); PluginResult parse_sync_response(const rapidjson::Document &root, bool initial_sync); PluginResult parse_notifications(const rapidjson::Value ¬ifications_json, std::function callback_func); PluginResult parse_sync_account_data(const rapidjson::Value &account_data_json); PluginResult parse_sync_room_data(const rapidjson::Value &rooms_json, bool initial_sync); void add_new_invites(); void parse_custom_emoji(const rapidjson::Value &custom_emoji_json); void load_custom_emoji_from_cache(); PluginResult get_previous_room_messages(RoomData *room_data, bool latest_messages, size_t &num_new_messages, bool *reached_end = nullptr); void events_add_user_info(const rapidjson::Value &events_json, RoomData *room_data, int64_t timestamp); std::shared_ptr parse_user_info(const rapidjson::Value &json, const std::string &user_id, RoomData *room_data, int64_t timestamp); void events_set_user_read_marker(const rapidjson::Value &events_json, RoomData *room_data, std::shared_ptr &me); // Returns the number of messages added size_t events_add_messages(const rapidjson::Value &events_json, RoomData *room_data, MessageDirection message_dir, bool has_unread_notifications); void events_set_room_info(const rapidjson::Value &events_json, RoomData *room_data, int64_t timestamp); void set_room_info_to_users_if_empty(RoomData *room, const std::string &room_creator_user_id); void events_add_pinned_events(const rapidjson::Value &events_json, RoomData *room_data); void events_add_room_to_tags(const rapidjson::Value &events_json, RoomData *room_data); void add_invites(const rapidjson::Value &invite_json); void remove_rooms(const rapidjson::Value &leave_json); PluginResult get_pinned_events(RoomData *room, std::vector &pinned_events); std::shared_ptr parse_message_event(const rapidjson::Value &event_item_json, RoomData *room_data); PluginResult upload_file(const std::string &filepath, std::string filename, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg, bool upload_thumbnail = true, bool bypass_proxy = false); void add_room(std::unique_ptr room); void remove_room(const std::string &room_id); // Returns false if an invite to the room already exists bool set_invite(const std::string &room_id, Invite invite); // Returns true if an invite for |room_id| exists bool remove_invite(const std::string &room_id); void set_next_batch(std::string new_next_batch, bool set_initial_sync); std::string get_next_batch(); void set_next_notifications_token(std::string new_next_token); std::string get_next_notifications_token(); std::shared_ptr get_user_by_id(RoomData *room, const std::string &user_id, bool *is_new_user = nullptr, bool create_if_not_found = true); std::string get_filter_cached(); void load_silenced_invites(); private: MessageQueue> ui_thread_tasks; std::vector> rooms; std::unordered_map room_data_by_id; // value is an index into |rooms| std::recursive_mutex room_data_mutex; std::string my_user_id; std::string access_token; std::string homeserver; std::string homeserver_domain; std::string well_known_base_url; std::optional upload_limit; std::string next_batch; std::string next_notifications_token; std::mutex next_batch_mutex; bool initial_sync_finished = false; bool matrix_instance_already_running = false; std::unordered_map invites; std::mutex invite_mutex; std::vector notifications; std::unordered_set notifications_by_event_id; std::mutex notifications_mutex; std::thread sync_thread; std::thread notification_thread; bool sync_running = false; bool sync_failed = false; bool finished_fetching_notifications = false; std::string sync_fail_reason; MatrixDelegate *delegate = nullptr; std::optional filter_cached; std::vector> invite_rooms; std::unordered_map custom_emoji_by_key; std::unordered_set silenced_invites; std::unordered_map qm_read_markers_by_room_cache; MessageQueue> decrypt_task; std::thread decrypt_thread; }; }