aboutsummaryrefslogtreecommitdiff
path: root/plugins/Matrix.hpp
blob: 5819420a047af8fc8210e29c2b2cb15f96627af3 (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
#pragma once

#include "../include/FileAnalyzer.hpp"
#include "Plugin.hpp"
#include "Page.hpp"
#include <SFML/Graphics/Color.hpp>
#include <unordered_map>
#include <mutex>
#include <json/value.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
    };

    struct Message {
        std::shared_ptr<UserInfo> user;
        std::string event_id;
        std::string body;
        std::string url;
        std::string thumbnail_url;
        std::string replaces_event_id;
        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, returns the number of inserted elements
        size_t prepend_messages_reverse(std::vector<std::shared_ptr<Message>> new_messages);
        // Ignores duplicates, returns the number of inserted elements
        size_t append_messages(std::vector<std::shared_ptr<Message>> new_messages);

        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;

        std::string id;
        std::string name;
        std::string avatar_url;
        std::string prev_batch;
        bool initial_fetch_finished = false;
        size_t last_read_index = 0;
    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;
    };

    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 RoomSyncMessages = std::unordered_map<std::shared_ptr<RoomData>, std::vector<std::shared_ptr<Message>>>;

    class Matrix {
    public:
        PluginResult sync(RoomSyncMessages &room_messages);
        PluginResult get_joined_rooms(BodyItems &result_items);
        PluginResult get_all_synced_room_messages(const std::string &room_id, BodyItems &result_items);
        PluginResult get_new_room_messages(const std::string &room_id, BodyItems &result_items);
        PluginResult get_previous_room_messages(const std::string &room_id, BodyItems &result_items);

        // |url| should only be set when uploading media.
        // TODO: Make api better.
        PluginResult post_message(const std::string &room_id, 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(const std::string &room_id, const std::string &body, void *relates_to);
        // |relates_to| is from |BodyItem.userdata| and is of type |Message*|
        PluginResult post_edit(const std::string &room_id, const std::string &body, void *relates_to);
        
        PluginResult post_file(const std::string &room_id, 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(const std::string &room_id, void *message, std::string &err_msg);

        PluginResult load_and_verify_cached_session();

        PluginResult on_start_typing(const std::string &room_id);
        PluginResult on_stop_typing(const std::string &room_id);

        PluginResult set_read_marker(const std::string &room_id, 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(const std::string &room_id);

        bool use_tor = false;
    private:
        PluginResult sync_response_to_body_items(const Json::Value &root, RoomSyncMessages &room_messages);
        PluginResult get_previous_room_messages(std::shared_ptr<RoomData> &room_data);
        void events_add_user_info(const Json::Value &events_json, RoomData *room_data);
        void events_add_user_read_markers(const Json::Value &events_json, RoomData *room_data);
        void events_add_messages(const Json::Value &events_json, std::shared_ptr<RoomData> &room_data, MessageDirection message_dir, RoomSyncMessages *room_messages, bool has_unread_notifications);
        void events_set_room_name(const Json::Value &events_json, RoomData *room_data);
        PluginResult upload_file(const std::string &room_id, 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);

        std::shared_ptr<RoomData> get_room_by_id(const std::string &id);
        void add_room(std::shared_ptr<RoomData> room);
        DownloadResult download_json(Json::Value &result, const std::string &url, std::vector<CommandArg> additional_args, bool use_browser_useragent = false, std::string *err_msg = nullptr) const;
    private:
        std::unordered_map<std::string, std::shared_ptr<RoomData>> room_data_by_id;
        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;
    };
}