aboutsummaryrefslogtreecommitdiff
path: root/plugins/Matrix.hpp
blob: c3c5539764e5d5fa789530c673b89ffec7251fe1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#pragma once

#include "../include/FileAnalyzer.hpp"
#include "Plugin.hpp"
#include "Page.hpp"
#include <SFML/Graphics/Color.hpp>
#include <unordered_map>
#include <mutex>
#include <rapidjson/fwd.h>

namespace QuickMedia {
    // 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<Tab> &result_tabs) override {
            (void)title;
            (void)url;
            (void)result_tabs;
            return PluginResult::ERR;
        }
        bool is_video_page() const override { return true; }
    };

    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<UserInfo> 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<UserInfo> get_user_by_id(const std::string &user_id);
        void add_user(std::shared_ptr<UserInfo> user);

        void set_user_read_marker(std::shared_ptr<UserInfo> &user, const std::string &event_id);
        std::string get_user_read_marker(std::shared_ptr<UserInfo> &user);

        // Ignores duplicates
        void prepend_messages_reverse(const std::vector<std::shared_ptr<Message>> &new_messages);
        // Ignores duplicates
        void append_messages(const std::vector<std::shared_ptr<Message>> &new_messages);

        void append_pinned_events(std::vector<std::string> new_pinned_events);

        std::shared_ptr<Message> get_message_by_id(const std::string &id);

        std::vector<std::shared_ptr<UserInfo>> get_users_excluding_me(const std::string &my_user_id);

        void acquire_room_lock();
        void release_room_lock();

        const std::vector<std::shared_ptr<Message>>& get_messages_thread_unsafe() const;
        const std::vector<std::string>& get_pinned_events_unsafe() const;

        std::string id;
        std::string name;
        std::string avatar_url;
        std::string prev_batch;
        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<std::string, std::shared_ptr<Message>> fetched_messages_by_event_id;
    private:
        std::mutex user_mutex;
        std::mutex room_mutex;
        // 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<std::string, std::shared_ptr<UserInfo>> user_info_by_user_id;
        std::vector<std::shared_ptr<Message>> messages;
        std::unordered_map<std::string, std::shared_ptr<Message>> message_by_event_id;
        std::vector<std::string> pinned_events;
    };

    enum class MessageDirection {
        BEFORE,
        AFTER
    };

    struct UploadInfo {
        ContentType content_type;
        size_t file_size;
        std::optional<Dimensions> dimensions;
        std::optional<double> duration_seconds;
        std::string content_uri;
    };

    using Messages = std::vector<std::shared_ptr<Message>>;

    struct SyncData {
        Messages messages;
        std::vector<std::string> pinned_events;
    };

    using RoomSyncData = std::unordered_map<RoomData*, SyncData>;
    using Rooms = std::vector<RoomData*>;

    bool message_contains_user_mention(const std::string &msg, const std::string &username);

    class Matrix {
    public:
        PluginResult sync(RoomSyncData &room_sync_data);
        void get_room_join_updates(Rooms &new_rooms);
        void get_all_synced_room_messages(RoomData *room, Messages &messages);
        void get_all_pinned_events(RoomData *room, std::vector<std::string> &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<UploadInfo> &file_info, const std::optional<UploadInfo> &thumbnail_info);
        // |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<UserInfo> get_me(RoomData *room);

        // Returns nullptr if message cant be found. Note: cached
        std::shared_ptr<Message> get_message_by_id(RoomData *room, const std::string &event_id);

        bool use_tor = false;
    private:
        PluginResult sync_response_to_body_items(const rapidjson::Document &root, RoomSyncData &room_sync_data);
        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, RoomSyncData *room_sync_data, 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, RoomSyncData &room_sync_data);
        std::shared_ptr<Message> 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);

        std::shared_ptr<Message> get_edited_message_original_message(RoomData *room_data, std::shared_ptr<Message> message);

        RoomData* get_room_by_id(const std::string &id);
        void add_room(std::unique_ptr<RoomData> room);
        DownloadResult download_json(rapidjson::Document &result, const std::string &url, std::vector<CommandArg> additional_args, bool use_browser_useragent = false, std::string *err_msg = nullptr) const;
    private:
        std::vector<std::unique_ptr<RoomData>> rooms;
        std::unordered_map<std::string, size_t> 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<int> upload_limit;
        std::string next_batch;
    };
}