Matrix.hpp 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. #pragma once
  2. #include "../include/FileAnalyzer.hpp"
  3. #include "../include/MessageQueue.hpp"
  4. #include "Plugin.hpp"
  5. #include "Page.hpp"
  6. #include <mglpp/graphics/Color.hpp>
  7. #include <unordered_map>
  8. #include <unordered_set>
  9. #include <map>
  10. #include <set>
  11. #include <mutex>
  12. #include <atomic>
  13. #include <thread>
  14. #include <rapidjson/fwd.h>
  15. namespace QuickMedia {
  16. struct RoomData;
  17. struct Message;
  18. static const int AUTHOR_MAX_LENGTH = 192;
  19. class Matrix;
  20. std::string extract_first_line_remove_newline_elipses(const std::string &str, size_t max_length);
  21. mgl::Color user_id_to_color(const std::string &user_id);
  22. // |image_max_size| 0, 0 means no max size
  23. 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));
  24. // |image_max_size| 0, 0 means no max size
  25. 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));
  26. std::string pantalaimon_url_to_homeserver_url(Matrix *matrix, const std::string &url);
  27. Message* get_latest_message_in_edit_chain(Message *message);
  28. 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);
  29. // Returns empty string on error
  30. std::string extract_user_name_from_user_id(const std::string &user_id);
  31. // Returns empty string on error
  32. std::string extract_user_name_from_email(const std::string &email);
  33. std::vector<std::string> matrix_extract_room_ids(const std::string &str);
  34. std::string remove_reply_formatting(Matrix *matrix, const Message *message, bool keep_formatted = false);
  35. struct MatrixChatBodyDecryptJob {
  36. enum class DecryptState {
  37. NOT_DECRYPTED,
  38. DECRYPTING,
  39. DECRYPTED,
  40. FAILED_TO_DECRYPT
  41. };
  42. std::string text;
  43. DecryptState decrypt_state = DecryptState::NOT_DECRYPTED;
  44. bool cancel = false;
  45. };
  46. class MatrixChatBodyItemData : public BodyItemExtra {
  47. public:
  48. enum class DecryptState {
  49. NOT_DECRYPTED,
  50. DECRYPTING,
  51. DECRYPTED
  52. };
  53. 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) {}
  54. ~MatrixChatBodyItemData();
  55. void draw_overlay(mgl::Window&, const Widgets &widgets) override;
  56. DecryptState decrypt_state = DecryptState::NOT_DECRYPTED;
  57. std::shared_ptr<MatrixChatBodyDecryptJob> decrypt_job;
  58. Matrix *matrix = nullptr;
  59. std::string text_to_decrypt;
  60. RoomData *room = nullptr;
  61. uint64_t gpg_decrypt_message_id = 0;
  62. };
  63. // TODO: Remove. Not needed anymore.
  64. struct TimestampedDisplayData {
  65. std::string data;
  66. time_t timestamp = 0; // In milliseconds
  67. // Force update by settings |new_timestamp| to 0
  68. bool set_data_if_newer(std::string new_data, time_t new_timestamp);
  69. };
  70. struct UserInfo {
  71. friend struct RoomData;
  72. UserInfo(RoomData *room, std::string user_id);
  73. UserInfo(RoomData *room, std::string user_id, std::string display_name, std::string avatar_url, time_t update_timestamp_ms);
  74. RoomData *room;
  75. const mgl::Color display_name_color;
  76. const std::string user_id;
  77. int power_level = 0;
  78. private:
  79. TimestampedDisplayData display_name;
  80. TimestampedDisplayData avatar_url;
  81. std::string read_marker_event_id;
  82. };
  83. enum class MessageType {
  84. TEXT,
  85. IMAGE,
  86. VIDEO,
  87. AUDIO,
  88. FILE,
  89. REDACTION,
  90. REACTION,
  91. MEMBERSHIP,
  92. SYSTEM,
  93. UNIMPLEMENTED
  94. };
  95. bool is_visual_media_message_type(MessageType message_type);
  96. bool is_system_message_type(MessageType message_type);
  97. enum class RelatedEventType {
  98. NONE,
  99. REPLY,
  100. EDIT,
  101. REDACTION,
  102. REACTION
  103. };
  104. struct MatrixEventUserInfo {
  105. RoomData *room;
  106. std::string user_id;
  107. std::optional<std::string> display_name;
  108. std::optional<std::string> avatar_url;
  109. };
  110. struct MatrixEventRoomInfo {
  111. RoomData *room;
  112. std::optional<std::string> name;
  113. std::optional<std::string> topic;
  114. std::optional<std::string> avatar_url;
  115. };
  116. enum class MatrixEventType {
  117. ADD_USER,
  118. REMOVE_USER,
  119. USER_INFO,
  120. ROOM_INFO
  121. };
  122. struct Message {
  123. std::shared_ptr<UserInfo> user;
  124. std::string event_id;
  125. std::string body;
  126. std::string url;
  127. std::string thumbnail_url;
  128. std::string related_event_id;
  129. mgl::vec2i thumbnail_size; // Set to {0, 0} if not specified
  130. RelatedEventType related_event_type = RelatedEventType::NONE;
  131. bool notification_mentions_me = false;
  132. bool body_is_formatted = false;
  133. std::string transaction_id;
  134. time_t timestamp = 0; // In milliseconds
  135. MessageType type;
  136. Message *replaces = nullptr;
  137. std::shared_ptr<Message> replaced_by = nullptr;
  138. // TODO: Store body item ref here
  139. };
  140. using Messages = std::vector<std::shared_ptr<Message>>;
  141. struct RoomData {
  142. std::shared_ptr<UserInfo> get_user_by_id(const std::string &user_id);
  143. void add_user(std::shared_ptr<UserInfo> user);
  144. void set_user_read_marker(std::shared_ptr<UserInfo> &user, const std::string &event_id);
  145. std::string get_user_read_marker(const std::shared_ptr<UserInfo> &user);
  146. std::string get_user_display_name(const std::shared_ptr<UserInfo> &user);
  147. std::string get_user_avatar_url(const std::shared_ptr<UserInfo> &user);
  148. // Set to empty to remove (in which case the display name will be the user id)
  149. bool set_user_display_name(std::shared_ptr<UserInfo> &user, std::string display_name, time_t update_timestamp_ms);
  150. bool set_user_avatar_url(std::shared_ptr<UserInfo> &user, std::string avatar_url, time_t update_timestamp_ms);
  151. // Ignores duplicates, returns the number of added messages
  152. size_t prepend_messages_reverse(const Messages &new_messages);
  153. // Ignores duplicates, returns the number of added messages
  154. size_t append_messages(const Messages &new_messages);
  155. std::shared_ptr<Message> get_message_by_id(const std::string &id);
  156. std::vector<std::shared_ptr<UserInfo>> get_users();
  157. std::vector<std::shared_ptr<UserInfo>> get_users_excluding_me(const std::string &my_user_id);
  158. void acquire_room_lock();
  159. void release_room_lock();
  160. const Messages& get_messages_thread_unsafe() const;
  161. const std::vector<std::string>& get_pinned_events_thread_unsafe() const;
  162. void clear_messages();
  163. bool has_prev_batch();
  164. void set_prev_batch(const std::string &new_prev_batch);
  165. std::string get_prev_batch();
  166. bool has_name();
  167. bool set_name(const std::string &new_name, time_t update_timestamp_ms);
  168. // TODO: Remove this
  169. std::string get_name();
  170. bool set_topic(const std::string &new_topic, time_t update_timestamp_ms);
  171. std::string get_topic();
  172. bool has_avatar_url();
  173. bool set_avatar_url(const std::string &new_avatar_url, time_t update_timestamp_ms);
  174. std::string get_avatar_url();
  175. void set_pinned_events(std::vector<std::string> new_pinned_events);
  176. std::set<std::string>& get_tags_thread_unsafe();
  177. void clear_data();
  178. std::string id;
  179. // These 5 variables are set by QuickMedia, not the matrix plugin
  180. bool initial_prev_messages_fetch = true;
  181. bool last_message_read = true;
  182. bool users_fetched = false;
  183. time_t last_read_message_timestamp = 0;
  184. std::shared_ptr<BodyItem> body_item;
  185. int offset_to_latest_message_text = 0;
  186. // These are messages fetched with |Matrix::get_message_by_id|. Needed to show replies, when replying to old message not part of /sync.
  187. // The value is nullptr if the message is fetched and cached but the event if referenced an invalid message.
  188. // TODO: Verify if replied to messages are also part of /sync; then this is not needed.
  189. std::unordered_map<std::string, std::shared_ptr<Message>> fetched_messages_by_event_id;
  190. size_t messages_read_index = 0;
  191. bool pinned_events_updated = false;
  192. bool name_is_fallback = false;
  193. bool avatar_is_fallback = false;
  194. std::atomic_int64_t last_message_timestamp = 0;
  195. std::atomic_int64_t read_marker_event_timestamp = 0;
  196. int notification_power_level = 50;
  197. size_t index = 0;
  198. std::atomic<uint64_t> gpg_decrypt_message_id = 0;
  199. private:
  200. std::mutex user_mutex;
  201. std::recursive_mutex room_mutex;
  202. TimestampedDisplayData name;
  203. TimestampedDisplayData topic;
  204. TimestampedDisplayData avatar_url;
  205. std::string prev_batch;
  206. // Each room has its own list of user data, even if multiple rooms has the same user
  207. // because users can have different display names and avatars in different rooms.
  208. std::unordered_map<std::string, std::shared_ptr<UserInfo>> user_info_by_user_id;
  209. Messages messages;
  210. std::unordered_map<std::string, std::shared_ptr<Message>> message_by_event_id;
  211. std::vector<std::string> pinned_events;
  212. std::set<std::string> tags;
  213. };
  214. struct RoomExtraData {
  215. std::string chat_message;
  216. std::string editing_message_id;
  217. };
  218. struct Invite {
  219. std::string room_name;
  220. std::string room_avatar_url;
  221. std::shared_ptr<UserInfo> invited_by;
  222. time_t timestamp = 0; // In milliseconds
  223. };
  224. enum class MessageDirection {
  225. BEFORE,
  226. AFTER
  227. };
  228. struct UploadInfo {
  229. ContentType content_type;
  230. int64_t file_size = 0;
  231. std::optional<Dimensions> dimensions;
  232. std::optional<double> duration_seconds;
  233. std::string content_uri;
  234. };
  235. struct SyncData {
  236. Messages messages;
  237. std::optional<std::vector<std::string>> pinned_events;
  238. };
  239. using Rooms = std::vector<RoomData*>;
  240. bool message_contains_user_mention(Matrix *matrix, const Message *message, const std::string &username, const std::string &user_id);
  241. bool message_contains_user_mention(const BodyItem *body_item, const std::string &username, const std::string &user_id);
  242. bool message_is_timeline(Message *message);
  243. void body_set_selected_item_by_url(Body *body, const std::string &url);
  244. std::string create_transaction_id();
  245. enum class MatrixPageType {
  246. ROOM_LIST,
  247. CHAT
  248. };
  249. enum class LeaveType {
  250. LEAVE,
  251. KICKED,
  252. BANNED
  253. };
  254. struct MatrixNotification {
  255. RoomData *room;
  256. std::string event_id;
  257. std::string sender_user_id;
  258. std::string body; // Without reply formatting
  259. time_t timestamp; // The timestamp in milliseconds or 0
  260. bool read;
  261. };
  262. // All of methods in this class are called in the main (ui) thread
  263. class MatrixDelegate {
  264. public:
  265. virtual ~MatrixDelegate() = default;
  266. virtual void join_room(RoomData *room) = 0;
  267. virtual void leave_room(RoomData *room, const std::string &event_id, LeaveType leave_type, const std::string &reason) = 0;
  268. // Note: calling |room| methods inside this function is not allowed
  269. virtual void room_add_tag(RoomData *room, const std::string &tag) = 0;
  270. // Note: calling |room| methods inside this function is not allowed
  271. virtual void room_remove_tag(RoomData *room, const std::string &tag) = 0;
  272. virtual void room_add_new_messages(RoomData *room, const Messages &messages, bool is_initial_sync, MessageDirection message_dir) = 0;
  273. virtual void room_clear_messages(RoomData *room) = 0;
  274. virtual void add_invite(const std::string &room_id, const Invite &invite) = 0;
  275. virtual void remove_invite(const std::string &room_id) = 0;
  276. virtual void add_unread_notification(MatrixNotification notification) = 0;
  277. virtual void add_user(MatrixEventUserInfo user_info) = 0;
  278. virtual void remove_user(MatrixEventUserInfo user_info) = 0;
  279. virtual void set_user_info(MatrixEventUserInfo user_info) = 0;
  280. virtual void set_room_info(MatrixEventRoomInfo room_info) = 0;
  281. virtual void set_room_as_read(RoomData *room) = 0;
  282. };
  283. class Matrix;
  284. class MatrixRoomsPage;
  285. class MatrixRoomTagsPage;
  286. class MatrixInvitesPage;
  287. class MatrixChatPage;
  288. class MatrixNotificationsPage;
  289. using UsersByRoom = std::unordered_map<RoomData*, std::unordered_map<std::string, MatrixEventUserInfo>>;
  290. class MatrixQuickMedia : public MatrixDelegate {
  291. public:
  292. MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page, MatrixInvitesPage *invites_page, MatrixNotificationsPage *notifications_page);
  293. void join_room(RoomData *room) override;
  294. void leave_room(RoomData *room, const std::string &event_id, LeaveType leave_type, const std::string &reason) override;
  295. void room_add_tag(RoomData *room, const std::string &tag) override;
  296. void room_remove_tag(RoomData *room, const std::string &tag) override;
  297. void room_add_new_messages(RoomData *room, const Messages &messages, bool is_initial_sync, MessageDirection message_dir) override;
  298. void room_clear_messages(RoomData *room) override;
  299. void add_invite(const std::string &room_id, const Invite &invite) override;
  300. void remove_invite(const std::string &room_id) override;
  301. void add_unread_notification(MatrixNotification notification) override;
  302. void add_user(MatrixEventUserInfo user_info) override;
  303. void remove_user(MatrixEventUserInfo user_info) override;
  304. void set_user_info(MatrixEventUserInfo user_info) override;
  305. void set_room_info(MatrixEventRoomInfo room_info) override;
  306. void for_each_user_in_room(RoomData *room, std::function<void(const MatrixEventUserInfo&)> callback);
  307. void set_room_as_read(RoomData *room) override;
  308. Program *program;
  309. Matrix *matrix;
  310. MatrixChatPage *chat_page;
  311. MatrixRoomsPage *rooms_page;
  312. MatrixRoomTagsPage *room_tags_page;
  313. MatrixInvitesPage *invites_page;
  314. MatrixNotificationsPage *notifications_page;
  315. private:
  316. void update_room_description(RoomData *room, const Messages &new_messages, bool is_initial_sync);
  317. private:
  318. std::map<RoomData*, std::shared_ptr<BodyItem>> room_body_item_by_room;
  319. std::map<RoomData*, std::shared_ptr<Message>> last_message_by_room;
  320. std::map<RoomData*, int> unread_mention_count_by_room;
  321. std::unordered_set<std::string> notifications_shown;
  322. UsersByRoom users_by_room;
  323. };
  324. class MatrixRoomsPage : public Page {
  325. public:
  326. MatrixRoomsPage(Program *program, Body *body, std::string title, MatrixRoomTagsPage *room_tags_page, SearchBar *search_bar);
  327. ~MatrixRoomsPage() override;
  328. const char* get_title() const override { return title.c_str(); }
  329. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  330. bool submit_is_async() const override { return false; }
  331. bool clear_search_after_submit() override { return true; }
  332. void add_body_item(std::shared_ptr<BodyItem> body_item);
  333. void move_room_to_top(RoomData *room);
  334. void remove_body_item_by_room_id(const std::string &room_id);
  335. void set_current_chat_page(MatrixChatPage *chat_page);
  336. void set_room_as_read(RoomData *room);
  337. void clear_search();
  338. MatrixQuickMedia *matrix_delegate = nullptr;
  339. Body *body = nullptr;
  340. private:
  341. std::string title;
  342. MatrixRoomTagsPage *room_tags_page = nullptr;
  343. MatrixChatPage *current_chat_page = nullptr;
  344. SearchBar *search_bar;
  345. };
  346. class MatrixRoomTagsPage : public Page {
  347. public:
  348. MatrixRoomTagsPage(Program *program, Body *body) : Page(program), body(body) {}
  349. const char* get_title() const override { return "Tags"; }
  350. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  351. bool submit_is_async() const override { return false; }
  352. bool clear_search_after_submit() override { return true; }
  353. void add_room_body_item_to_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag);
  354. void remove_room_body_item_from_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag);
  355. void move_room_to_top(RoomData *room);
  356. void remove_body_item_by_room_id(const std::string &room_id);
  357. void set_current_rooms_page(MatrixRoomsPage *rooms_page);
  358. MatrixQuickMedia *matrix_delegate = nullptr;
  359. private:
  360. struct TagData {
  361. std::shared_ptr<BodyItem> tag_item;
  362. std::vector<std::shared_ptr<BodyItem>> room_body_items;
  363. };
  364. Body *body;
  365. std::map<std::string, TagData> tag_body_items_by_name;
  366. MatrixRoomsPage *current_rooms_page = nullptr;
  367. };
  368. class MatrixInvitesPage : public Page {
  369. public:
  370. MatrixInvitesPage(Program *program, Matrix *matrix, Body *body);
  371. const char* get_title() const override { return title.c_str(); }
  372. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  373. bool submit_is_async() const override { return false; }
  374. bool clear_search_after_submit() override { return true; }
  375. void add_body_item(std::shared_ptr<BodyItem> body_item);
  376. void remove_body_item_by_room_id(const std::string &room_id);
  377. private:
  378. Matrix *matrix;
  379. Body *body;
  380. std::string title = "Invites (0)";
  381. size_t prev_invite_count = 0;
  382. };
  383. class MatrixInviteDetailsPage : public Page {
  384. public:
  385. MatrixInviteDetailsPage(Program *program, Matrix *matrix, MatrixInvitesPage *invites_page, std::string room_id, std::string title) :
  386. Page(program), matrix(matrix), invites_page(invites_page), room_id(std::move(room_id)), title(std::move(title)) {}
  387. const char* get_title() const override { return title.c_str(); }
  388. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  389. Matrix *matrix;
  390. MatrixInvitesPage *invites_page;
  391. const std::string room_id;
  392. const std::string title;
  393. };
  394. class MatrixSettingsPage : public Page {
  395. public:
  396. MatrixSettingsPage(Program *program, Matrix *matrix) : Page(program), matrix(matrix) {}
  397. const char* get_title() const override { return "Settings"; }
  398. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  399. private:
  400. Matrix *matrix;
  401. };
  402. class MatrixRoomInputPage : public Page {
  403. public:
  404. MatrixRoomInputPage(Program *program, Matrix *matrix) : Page(program), matrix(matrix) {}
  405. const char* get_title() const override { return "Enter the id of a room to join"; }
  406. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  407. bool allow_submit_no_selection() const override { return true; }
  408. private:
  409. Matrix *matrix;
  410. };
  411. class MatrixCustomEmojiPage : public LazyFetchPage {
  412. public:
  413. MatrixCustomEmojiPage(Program *program, Matrix *matrix) : LazyFetchPage(program), matrix(matrix) {}
  414. const char* get_title() const override { return "Custom emoji"; }
  415. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  416. PluginResult lazy_fetch(BodyItems &result_items) override;
  417. bool is_ready() override;
  418. private:
  419. Matrix *matrix;
  420. };
  421. class MatrixCustomEmojiRenameSelectPage : public LazyFetchPage {
  422. public:
  423. MatrixCustomEmojiRenameSelectPage(Program *program, Matrix *matrix) : LazyFetchPage(program), matrix(matrix) {}
  424. const char* get_title() const override { return "Select emoji to rename"; }
  425. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  426. PluginResult lazy_fetch(BodyItems &result_items) override;
  427. bool submit_is_async() const override { return false; }
  428. bool reload_on_page_change() override { return true; }
  429. private:
  430. Matrix *matrix;
  431. };
  432. class MatrixCustomEmojiRenamePage : public Page {
  433. public:
  434. MatrixCustomEmojiRenamePage(Program *program, Matrix *matrix, std::string emoji_key) : Page(program), matrix(matrix), emoji_key(std::move(emoji_key)) {}
  435. const char* get_title() const override { return "Enter a new name for the emoji"; }
  436. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  437. bool allow_submit_no_selection() const override { return true; }
  438. private:
  439. Matrix *matrix;
  440. std::string emoji_key;
  441. };
  442. class MatrixCustomEmojiDeletePage : public Page {
  443. public:
  444. MatrixCustomEmojiDeletePage(Program *program, Matrix *matrix, Body *body) : Page(program), matrix(matrix), body(body) {}
  445. const char* get_title() const override { return "Select emoji to delete"; }
  446. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  447. private:
  448. Matrix *matrix;
  449. Body *body;
  450. };
  451. // Only play one video. TODO: Play all videos in room, as related videos?
  452. class MatrixVideoPage : public VideoPage {
  453. public:
  454. MatrixVideoPage(Program *program, std::string filename) : VideoPage(program, ""), filename(std::move(filename)) {}
  455. const char* get_title() const override;
  456. std::string get_filename() override;
  457. std::string get_download_url_for_clipboard(int max_height) override;
  458. private:
  459. std::string filename;
  460. };
  461. using MatrixRoomInfoUpdateCallback = std::function<void(const MatrixEventRoomInfo &room_info)>;
  462. class MatrixChatPage : public Page {
  463. public:
  464. MatrixChatPage(Program *program, std::string room_id, MatrixRoomsPage *rooms_page, std::string jump_to_event_id = "");
  465. ~MatrixChatPage();
  466. const char* get_title() const override { return ""; }
  467. PageTypez get_type() const override { return PageTypez::CHAT; }
  468. void add_user(MatrixEventUserInfo user_info);
  469. void remove_user(MatrixEventUserInfo user_info);
  470. void set_user_info(MatrixEventUserInfo user_info);
  471. void set_room_info(MatrixEventRoomInfo room_info);
  472. void set_current_room(RoomData *room, Body *users_body, MatrixRoomInfoUpdateCallback room_info_update_callback);
  473. size_t get_num_users_in_current_room() const;
  474. void set_room_as_read(RoomData *room);
  475. const std::string room_id;
  476. MatrixRoomsPage *rooms_page = nullptr;
  477. Body *chat_body = nullptr;
  478. bool messages_tab_visible = false;
  479. bool is_regular_navigation = true;
  480. const std::string jump_to_event_id;
  481. private:
  482. void add_user_to_body_by_user_info(const MatrixEventUserInfo &user_info);
  483. private:
  484. RoomData *current_room = nullptr;
  485. Body *users_body = nullptr;
  486. MatrixRoomInfoUpdateCallback room_info_update_callback;
  487. std::unordered_map<std::string, BodyItem*> user_body_item_by_user_id;
  488. };
  489. class MatrixRoomDirectoryPage : public Page {
  490. public:
  491. MatrixRoomDirectoryPage(Program *program, Matrix *matrix) : Page(program), matrix(matrix) {}
  492. const char* get_title() const override { return "Room directory"; }
  493. bool allow_submit_no_selection() const override { return true; }
  494. bool clear_search_after_submit() override { return true; }
  495. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  496. private:
  497. Matrix *matrix;
  498. };
  499. class MatrixServerRoomListPage : public LazyFetchPage {
  500. public:
  501. MatrixServerRoomListPage(Program *program, Matrix *matrix, const std::string &server_name) : LazyFetchPage(program), matrix(matrix), server_name(server_name), current_page(0) {}
  502. const char* get_title() const override { return "Select a room to join"; }
  503. bool search_is_filter() override { return false; }
  504. PluginResult lazy_fetch(BodyItems &result_items) override;
  505. PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override;
  506. SearchResult search(const std::string &str, BodyItems &result_items) override;
  507. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  508. private:
  509. Matrix *matrix;
  510. const std::string server_name;
  511. std::string next_batch;
  512. std::string search_term;
  513. int current_page;
  514. };
  515. class MatrixNotificationsPage : public LazyFetchPage {
  516. public:
  517. MatrixNotificationsPage(Program *program, Matrix *matrix, Body *notifications_body, MatrixRoomsPage *all_rooms_page);
  518. const char* get_title() const override { return "Notifications"; }
  519. PluginResult submit(const SubmitArgs &args, std::vector<Tab>&) override;
  520. PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override;
  521. PluginResult lazy_fetch(BodyItems &result_items) override;
  522. bool is_ready() override;
  523. bool submit_is_async() const override { return false; }
  524. bool clear_search_after_submit() override { return true; }
  525. void add_notification(MatrixNotification notification);
  526. void set_room_as_read(RoomData *room);
  527. private:
  528. bool has_fetched = false;
  529. Matrix *matrix;
  530. Body *notifications_body;
  531. MatrixRoomsPage *all_rooms_page;
  532. // room id[event_id[]]
  533. std::unordered_map<std::string, std::unordered_map<std::string, std::shared_ptr<BodyItem>>> room_notifications;
  534. // Notifications are here until the notifications has been fetched, so that page handler doesn't the notifications
  535. std::unordered_map<std::string, std::unordered_map<std::string, MatrixNotification>> pending_room_notifications;
  536. };
  537. class MatrixInviteUserPage : public Page {
  538. public:
  539. MatrixInviteUserPage(Program *program, Matrix *matrix, std::string room_id) : Page(program), matrix(matrix), room_id(std::move(room_id)) {}
  540. const char* get_title() const override { return "Invite user"; }
  541. bool search_is_filter() override { return false; }
  542. SearchResult search(const std::string &str, BodyItems &result_items) override;
  543. PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override;
  544. bool allow_submit_no_selection() const override { return true; }
  545. private:
  546. Matrix *matrix;
  547. std::string room_id;
  548. };
  549. struct CustomEmoji {
  550. std::string url;
  551. mgl::vec2i size;
  552. };
  553. class Matrix {
  554. public:
  555. Matrix(bool matrix_instance_already_running);
  556. // TODO: Make this return the Matrix object instead, to force users to call start_sync
  557. bool start_sync(MatrixDelegate *delegate, bool &cached);
  558. void stop_sync();
  559. bool is_initial_sync_finished();
  560. // Returns true if initial sync failed, and |err_msg| is set to the error reason in that case
  561. bool did_initial_sync_fail(std::string &err_msg);
  562. bool has_finished_fetching_notifications() const;
  563. void get_room_sync_data(RoomData *room, SyncData &sync_data);
  564. void get_all_synced_room_messages(RoomData *room, Messages &messages);
  565. void get_all_pinned_events(RoomData *room, std::vector<std::string> &events);
  566. PluginResult get_messages_in_direction(RoomData *room, const std::string &token, MessageDirection message_dir, Messages &messages, std::string &new_token);
  567. PluginResult get_previous_room_messages(RoomData *room, Messages &messages, bool latest_messages = false, bool *reached_end = nullptr);
  568. PluginResult get_previous_notifications(std::function<void(const MatrixNotification&)> callback_func);
  569. void get_cached_notifications(std::function<void(const MatrixNotification&)> callback_func);
  570. // |url| should only be set when uploading media.
  571. // TODO: Make api better.
  572. PluginResult post_message(RoomData *room, const std::string &body, std::string &event_id_response, const std::optional<UploadInfo> &file_info, const std::optional<UploadInfo> &thumbnail_info, const std::string &msgtype = "", const std::string &custom_transaction_id = "");
  573. // |relates_to| is from |BodyItem.userdata| and is of type |Message*|
  574. // If |custom_transaction_id| is empty, then a new transaction id is generated
  575. 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<UploadInfo> &file_info = std::nullopt, const std::optional<UploadInfo> &thumbnail_info = std::nullopt);
  576. // |relates_to| is from |BodyItem.userdata| and is of type |Message*|
  577. PluginResult post_edit(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response, const std::string &custom_transaction_id = "");
  578. // |relates_to| is from |BodyItem.userdata| and is of type |Message*|
  579. PluginResult post_reaction(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response, const std::string &custom_transaction_id = "");
  580. // If filename is empty then the filename is extracted from filepath
  581. 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);
  582. PluginResult upload_custom_emoji(const std::string &filepath, const std::string &key, std::string &mxc_url, std::string &err_msg);
  583. bool delete_custom_emoji(const std::string &key);
  584. bool rename_custom_emoji(const std::string &key, const std::string &new_key);
  585. bool does_custom_emoji_with_name_exist(const std::string &name);
  586. std::unordered_map<std::string, CustomEmoji> get_custom_emojis();
  587. PluginResult login(const std::string &username, const std::string &password, const std::string &homeserver, std::string &err_msg);
  588. PluginResult logout();
  589. // |message| is from |BodyItem.userdata| and is of type |Message*|
  590. PluginResult delete_message(RoomData *room, void *message, std::string &err_msg);
  591. PluginResult pin_message(RoomData *room, const std::string &event_id);
  592. PluginResult unpin_message(RoomData *room, const std::string &event_id);
  593. PluginResult load_cached_session();
  594. PluginResult on_start_typing(RoomData *room);
  595. PluginResult on_stop_typing(RoomData *room);
  596. PluginResult set_read_marker(RoomData *room, const std::string &event_id, int64_t event_timestamp);
  597. PluginResult join_room(const std::string &room_id_or_name);
  598. PluginResult leave_room(const std::string &room_id);
  599. bool is_invite_silenced(const std::string &room_id, int64_t timestamp);
  600. void silence_invite(const std::string &room_id, int64_t timestamp);
  601. // If |since| is empty, then the first page is fetched
  602. PluginResult get_public_rooms(const std::string &server, const std::string &search_term, const std::string &since, BodyItems &rooms, std::string &next_batch);
  603. PluginResult search_user(const std::string &search_term, unsigned int limit, BodyItems &result_items);
  604. PluginResult invite_user(const std::string &room_id, const std::string &user_id);
  605. // |message| is from |BodyItem.userdata| and is of type |Message*|
  606. bool was_message_posted_by_me(void *message);
  607. std::string message_get_author_displayname(Message *message) const;
  608. // Cached
  609. PluginResult get_config(int64_t *upload_size);
  610. std::shared_ptr<UserInfo> get_me(RoomData *room);
  611. const std::string& get_homeserver_domain() const;
  612. std::string get_remote_homeserver_url() const;
  613. // Returns nullptr if message cant be found. Note: cached
  614. std::shared_ptr<Message> get_message_by_id(RoomData *room, const std::string &event_id);
  615. PluginResult get_message_context(RoomData *room, const std::string &event_id, std::shared_ptr<Message> &message, Messages &before_messages, Messages &after_messages, std::string &before_token, std::string &after_token);
  616. void clear_previous_messages_token(RoomData *room);
  617. RoomData* get_room_by_id(const std::string &id);
  618. void update_room_users(RoomData *room);
  619. std::string get_media_url(const std::string &mxc_id);
  620. RoomExtraData& get_room_extra_data(RoomData *room);
  621. void append_system_message(RoomData *room_data, std::shared_ptr<Message> message);
  622. std::string body_to_formatted_body(RoomData *room, const std::string &body);
  623. void on_exit_room(RoomData *room);
  624. void async_decrypt_message(std::shared_ptr<MatrixChatBodyDecryptJob> decrypt_job);
  625. MatrixDelegate* get_delegate();
  626. bool is_another_instance_running() const { return matrix_instance_already_running; }
  627. void mark_other_notification_as_read(const std::string &event_id);
  628. bool is_other_notification_read(const std::string &event_id) const;
  629. // Calls the |MatrixDelegate| pending events.
  630. // Should be called from the main (ui) thread
  631. void update();
  632. private:
  633. void trigger_event(RoomData *room, MatrixEventType type, MatrixEventUserInfo user_info);
  634. void trigger_event(RoomData *room, MatrixEventType type, MatrixEventRoomInfo room_info);
  635. void formatted_body_add_line(RoomData *room, std::string &formatted_body, const std::string &line_str, const std::unordered_map<std::string, CustomEmoji> &custom_emojis);
  636. void replace_mentions(RoomData *room, std::string &text);
  637. std::string create_formatted_body_for_message_reply(RoomData *room, const Message *message, const std::string &body);
  638. std::string create_formatted_body_for_message_edit(RoomData *room, const Message *replied_to_message, const std::string &body);
  639. PluginResult set_pinned_events(RoomData *room, const std::vector<std::string> &pinned_events, bool is_add);
  640. PluginResult set_qm_last_read_message_timestamp(RoomData *room, int64_t timestamp);
  641. bool load_qm_read_markers_from_account_data();
  642. PluginResult parse_sync_response(const rapidjson::Document &root, bool initial_sync);
  643. PluginResult parse_notifications(const rapidjson::Value &notifications_json, std::function<void(const MatrixNotification&)> callback_func);
  644. PluginResult parse_sync_account_data(const rapidjson::Value &account_data_json);
  645. PluginResult parse_sync_room_data(const rapidjson::Value &rooms_json, bool initial_sync);
  646. void add_new_invites();
  647. void parse_custom_emoji(const rapidjson::Value &custom_emoji_json);
  648. void load_custom_emoji_from_cache();
  649. void load_other_notifications();
  650. PluginResult get_previous_room_messages(RoomData *room_data, bool latest_messages, size_t &num_new_messages, bool *reached_end = nullptr);
  651. void events_add_user_info(const rapidjson::Value &events_json, RoomData *room_data, int64_t timestamp);
  652. std::shared_ptr<UserInfo> parse_user_info(const rapidjson::Value &json, const std::string &user_id, RoomData *room_data, int64_t timestamp);
  653. void events_set_user_read_marker(const rapidjson::Value &events_json, RoomData *room_data, std::shared_ptr<UserInfo> &me);
  654. // Returns the number of messages added
  655. size_t events_add_messages(const rapidjson::Value &events_json, RoomData *room_data, MessageDirection message_dir, bool has_unread_notifications);
  656. void events_set_room_info(const rapidjson::Value &events_json, RoomData *room_data, int64_t timestamp);
  657. void set_room_info_to_users_if_empty(RoomData *room, const std::string &room_creator_user_id);
  658. void events_add_pinned_events(const rapidjson::Value &events_json, RoomData *room_data);
  659. void events_add_room_to_tags(const rapidjson::Value &events_json, RoomData *room_data);
  660. void add_invites(const rapidjson::Value &invite_json);
  661. void remove_rooms(const rapidjson::Value &leave_json);
  662. PluginResult get_pinned_events(RoomData *room, std::vector<std::string> &pinned_events);
  663. std::shared_ptr<Message> parse_message_event(const rapidjson::Value &event_item_json, RoomData *room_data);
  664. 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);
  665. void add_room(std::unique_ptr<RoomData> room);
  666. void remove_room(const std::string &room_id);
  667. // Returns false if an invite to the room already exists
  668. bool set_invite(const std::string &room_id, Invite invite);
  669. // Returns true if an invite for |room_id| exists
  670. bool remove_invite(const std::string &room_id);
  671. void set_next_batch(std::string new_next_batch, bool set_initial_sync);
  672. std::string get_next_batch();
  673. void set_next_notifications_token(std::string new_next_token);
  674. std::string get_next_notifications_token();
  675. std::shared_ptr<UserInfo> get_user_by_id(RoomData *room, const std::string &user_id, bool *is_new_user = nullptr, bool create_if_not_found = true);
  676. std::string get_filter_cached();
  677. void load_silenced_invites();
  678. private:
  679. MessageQueue<std::function<void()>> ui_thread_tasks;
  680. std::vector<std::unique_ptr<RoomData>> rooms;
  681. std::unordered_map<std::string, size_t> room_data_by_id; // value is an index into |rooms|
  682. std::recursive_mutex room_data_mutex;
  683. std::string my_user_id;
  684. std::string access_token;
  685. std::string homeserver;
  686. std::string homeserver_domain;
  687. std::string well_known_base_url;
  688. std::optional<int64_t> upload_limit;
  689. std::string next_batch;
  690. std::string next_notifications_token;
  691. std::mutex next_batch_mutex;
  692. bool initial_sync_finished = false;
  693. bool matrix_instance_already_running = false;
  694. std::unordered_map<std::string, Invite> invites;
  695. std::mutex invite_mutex;
  696. std::vector<MatrixNotification> notifications;
  697. std::unordered_set<std::string> notifications_by_event_id;
  698. std::mutex notifications_mutex;
  699. std::thread sync_thread;
  700. std::thread notification_thread;
  701. bool sync_running = false;
  702. bool sync_failed = false;
  703. bool finished_fetching_notifications = false;
  704. std::string sync_fail_reason;
  705. MatrixDelegate *delegate = nullptr;
  706. std::optional<std::string> filter_cached;
  707. std::vector<std::unique_ptr<RoomData>> invite_rooms;
  708. std::unordered_map<std::string, CustomEmoji> custom_emoji_by_key;
  709. std::unordered_set<std::string> silenced_invites;
  710. std::unordered_map<std::string, int64_t> qm_read_markers_by_room_cache;
  711. std::unordered_set<std::string> other_notifications_read;
  712. MessageQueue<std::shared_ptr<MatrixChatBodyDecryptJob>> decrypt_task;
  713. std::thread decrypt_thread;
  714. std::unordered_map<std::string, RoomExtraData> room_extra_data_by_id;
  715. };
  716. }