#pragma once #include "../include/FileAnalyzer.hpp" #include "Plugin.hpp" #include "Page.hpp" #include #include #include #include #include namespace QuickMedia { struct RoomData; struct UserInfo { friend struct RoomData; std::string user_id; std::string display_name; std::string avatar_url; sf::Color display_name_color; private: std::string read_marker_event_id; }; enum class MessageType { TEXT, IMAGE, VIDEO, AUDIO, FILE, REDACTION }; enum class RelatedEventType { NONE, REPLY, EDIT, REDACTION }; 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; sf::Vector2i thumbnail_size; // Set to {0, 0} if not specified RelatedEventType related_event_type; bool mentions_me = false; time_t timestamp = 0; MessageType type; }; 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(std::shared_ptr &user); // Ignores duplicates void prepend_messages_reverse(const std::vector> &new_messages); // Ignores duplicates void append_messages(const std::vector> &new_messages); std::shared_ptr get_message_by_id(const std::string &id); std::vector> get_users_excluding_me(const std::string &my_user_id); void acquire_room_lock(); void release_room_lock(); const std::vector>& get_messages_thread_unsafe() const; const std::vector& get_pinned_events_unsafe() const; bool has_prev_batch(); void set_prev_batch(const std::string &new_prev_batch); std::string get_prev_batch(); bool has_name(); void set_name(const std::string &new_name); // TODO: Remove this std::string get_name(); bool has_avatar_url(); void set_avatar_url(const std::string &new_avatar_url); std::string get_avatar_url(); void set_pinned_events(std::vector new_pinned_events); std::set& get_tags_unsafe(); std::string id; bool initial_fetch_finished = false; // These 4 variables are set by QuickMedia, not the matrix plugin bool last_message_read = true; time_t last_read_message_timestamp = 0; bool has_unread_mention = false; void *userdata = nullptr; // Pointer to BodyItem. Note: this has to be valid as long as the room is valid // 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; size_t index; private: std::mutex user_mutex; std::mutex room_mutex; std::string name; std::string 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; std::vector> messages; std::unordered_map> message_by_event_id; std::vector pinned_events; std::set tags; }; enum class MessageDirection { BEFORE, AFTER }; struct UploadInfo { ContentType content_type; size_t file_size; std::optional dimensions; std::optional duration_seconds; std::string content_uri; }; using Messages = std::vector>; struct SyncData { Messages messages; std::optional> pinned_events; std::optional> tags; }; using Rooms = std::vector; bool message_contains_user_mention(const std::string &msg, const std::string &username); enum class MatrixPageType { ROOM_LIST, CHAT }; class MatrixDelegate { public: virtual ~MatrixDelegate() = default; virtual void room_create(RoomData *room) = 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) = 0; virtual void update(MatrixPageType page_type) { (void)page_type; } }; class Matrix; class MatrixRoomsPage; class MatrixRoomTagsPage; class MatrixQuickMedia : public MatrixDelegate { public: MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page); void room_create(RoomData *room) 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) override; void update(MatrixPageType page_type) override; Program *program; Matrix *matrix; MatrixRoomsPage *rooms_page; MatrixRoomTagsPage *room_tags_page; private: struct RoomMessagesData { Messages messages; bool is_initial_sync; }; std::vector> room_body_items; std::map> room_body_item_by_room; std::map pending_room_messages; std::mutex pending_room_messages_mutex; }; class MatrixRoomsPage : public Page { public: MatrixRoomsPage(Program *program, Body *body, std::string title, MatrixRoomTagsPage *room_tags_page = nullptr); ~MatrixRoomsPage() override; const char* get_title() const override { return title.c_str(); } PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override; void update() override; void add_body_item(std::shared_ptr body_item); void move_room_to_top(RoomData *room); MatrixQuickMedia *matrix_delegate = nullptr; private: std::mutex mutex; std::vector> room_body_items; Body *body; std::string title; MatrixRoomTagsPage *room_tags_page; }; 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 std::string &title, const std::string &url, std::vector &result_tabs) override; void update() override; 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); MatrixQuickMedia *matrix_delegate = nullptr; MatrixRoomsPage *current_rooms_page = nullptr; private: struct TagData { std::shared_ptr tag_item; std::vector> room_body_items; }; std::mutex mutex; Body *body; std::map tag_body_items_by_name; std::map>> add_room_body_items_by_tags; std::map>> remove_room_body_items_by_tags; }; // Dummy, only play one video. TODO: Play all videos in room, as related videos? class MatrixVideoPage : public Page { public: MatrixVideoPage(Program *program) : Page(program) {} const char* get_title() const override { return ""; } PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override { (void)title; (void)url; (void)result_tabs; return PluginResult::ERR; } PageTypez get_type() const override { return PageTypez::VIDEO; } }; class MatrixChatPage : public Page { public: MatrixChatPage(Program *program, std::string room_id) : Page(program), room_id(std::move(room_id)) {} const char* get_title() const override { return ""; } PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override { (void)title; (void)url; (void)result_tabs; return PluginResult::ERR; } PageTypez get_type() const override { return PageTypez::CHAT; } void update() override; const std::string room_id; MatrixQuickMedia *matrix_delegate = nullptr; }; class Matrix { public: void start_sync(MatrixDelegate *delegate); void stop_sync(); bool is_initial_sync_finished() 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_previous_room_messages(RoomData *room, Messages &messages); // |url| should only be set when uploading media. // TODO: Make api better. PluginResult post_message(RoomData *room, const std::string &body, const std::optional &file_info, const std::optional &thumbnail_info, const std::string &msgtype = ""); // |relates_to| is from |BodyItem.userdata| and is of type |Message*| PluginResult post_reply(RoomData *room, const std::string &body, void *relates_to); // |relates_to| is from |BodyItem.userdata| and is of type |Message*| PluginResult post_edit(RoomData *room, const std::string &body, void *relates_to); PluginResult post_file(RoomData *room, const std::string &filepath, std::string &err_msg); 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 load_and_verify_cached_session(); PluginResult on_start_typing(RoomData *room); PluginResult on_stop_typing(RoomData *room); PluginResult set_read_marker(RoomData *room, const Message *message); // |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(int *upload_size); std::shared_ptr get_me(RoomData *room); // Returns nullptr if message cant be found. Note: cached std::shared_ptr get_message_by_id(RoomData *room, const std::string &event_id); RoomData* get_room_by_id(const std::string &id); bool use_tor = false; private: PluginResult parse_sync_response(const rapidjson::Document &root, MatrixDelegate *delegate); PluginResult parse_sync_account_data(const rapidjson::Value &account_data_json, std::optional> &dm_rooms); PluginResult parse_sync_room_data(const rapidjson::Value &rooms_json, MatrixDelegate *delegate); PluginResult get_previous_room_messages(RoomData *room_data); void events_add_user_info(const rapidjson::Value &events_json, RoomData *room_data); void events_add_user_read_markers(const rapidjson::Value &events_json, RoomData *room_data); void events_add_messages(const rapidjson::Value &events_json, RoomData *room_data, MessageDirection message_dir, MatrixDelegate *delegate, bool has_unread_notifications); void events_set_room_name(const rapidjson::Value &events_json, RoomData *room_data); 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, MatrixDelegate *delegate); std::shared_ptr parse_message_event(const rapidjson::Value &event_item_json, RoomData *room_data); PluginResult upload_file(RoomData *room, const std::string &filepath, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg); void add_room(std::unique_ptr room); DownloadResult download_json(rapidjson::Document &result, const std::string &url, std::vector additional_args, bool use_browser_useragent = false, std::string *err_msg = nullptr) const; private: std::vector> rooms; std::unordered_map room_data_by_id; // value is an index into |rooms| size_t room_list_read_index = 0; std::mutex room_data_mutex; std::string user_id; std::string username; std::string access_token; std::string homeserver; std::optional upload_limit; std::string next_batch; std::thread sync_thread; bool sync_running = false; }; }