summaryrefslogtreecommitdiff
path: root/discord/discord.hpp
blob: 5bfb29a7925b6ac328fedcde6876f4a2db54c70c (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
#pragma once
#include "websocket.hpp"
#include "http.hpp"
#include "objects.hpp"
#include "store.hpp"
#include <sigc++/sigc++.h>
#include <nlohmann/json.hpp>
#include <thread>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <mutex>
#include <zlib.h>
#include <glibmm.h>
#include <queue>

// bruh
#ifdef GetMessage
    #undef GetMessage
#endif

// https://stackoverflow.com/questions/29775153/stopping-long-sleep-threads/29775639#29775639
class HeartbeatWaiter {
public:
    template<class R, class P>
    bool wait_for(std::chrono::duration<R, P> const &time) const {
        std::unique_lock<std::mutex> lock(m);
        return !cv.wait_for(lock, time, [&] { return terminate; });
    }

    void kill() {
        std::unique_lock<std::mutex> lock(m);
        terminate = true;
        cv.notify_all();
    }

private:
    mutable std::condition_variable cv;
    mutable std::mutex m;
    bool terminate = false;
};

class Abaddon;
class DiscordClient {
    friend class Abaddon;

public:
    static const constexpr char *DiscordGateway = "wss://gateway.discord.gg/?v=8&encoding=json&compress=zlib-stream";
    static const constexpr char *DiscordAPI = "https://discord.com/api";
    static const constexpr char *GatewayIdentity = "Discord";

public:
    DiscordClient();
    void Start();
    void Stop();
    bool IsStarted() const;

    using guilds_type = Store::guilds_type;
    using channels_type = Store::channels_type;
    using messages_type = Store::messages_type;
    using users_type = Store::users_type;
    using roles_type = Store::roles_type;
    using members_type = Store::members_type;
    using permission_overwrites_type = Store::permission_overwrites_type;

    std::unordered_set<Snowflake> GetGuildsID() const;
    const guilds_type &GetGuilds() const;
    const User &GetUserData() const;
    const UserSettings &GetUserSettings() const;
    std::vector<Snowflake> GetUserSortedGuilds() const;
    std::set<Snowflake> GetMessagesForChannel(Snowflake id) const;
    std::set<Snowflake> GetPrivateChannels() const;

    void FetchInviteData(std::string code, std::function<void(Invite)> cb, std::function<void(bool)> err);
    void UpdateSettingsGuildPositions(const std::vector<Snowflake> &pos);
    void FetchMessagesInChannel(Snowflake id, std::function<void(const std::vector<Snowflake> &)> cb);
    void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, std::function<void(const std::vector<Snowflake> &)> cb);
    const Message *GetMessage(Snowflake id) const;
    const Channel *GetChannel(Snowflake id) const;
    const User *GetUser(Snowflake id) const;
    const Role *GetRole(Snowflake id) const;
    const Guild *GetGuild(Snowflake id) const;
    const GuildMember *GetMember(Snowflake user_id, Snowflake guild_id) const;
    const PermissionOverwrite *GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const;
    const Emoji *GetEmoji(Snowflake id) const;
    Snowflake GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color = false) const;
    Snowflake GetMemberHighestRole(Snowflake guild_id, Snowflake user_id) const;
    std::unordered_set<Snowflake> GetUsersInGuild(Snowflake id) const;
    std::unordered_set<Snowflake> GetRolesInGuild(Snowflake id) const;

    bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const;
    bool HasChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const;
    Permission ComputePermissions(Snowflake member_id, Snowflake guild_id) const;
    Permission ComputeOverwrites(Permission base, Snowflake member_id, Snowflake channel_id) const;
    bool CanManageMember(Snowflake channel_id, Snowflake actor, Snowflake target) const; // kick, ban, edit nickname (cant think of a better name)

    void SendChatMessage(std::string content, Snowflake channel);
    void DeleteMessage(Snowflake channel_id, Snowflake id);
    void EditMessage(Snowflake channel_id, Snowflake id, std::string content);
    void SendLazyLoad(Snowflake id);
    void JoinGuild(std::string code);
    void LeaveGuild(Snowflake id);
    void KickUser(Snowflake user_id, Snowflake guild_id);
    void BanUser(Snowflake user_id, Snowflake guild_id); // todo: reason, delete messages

    void UpdateToken(std::string token);

private:
    static const constexpr int InflateChunkSize = 0x10000;
    std::vector<uint8_t> m_compressed_buf;
    std::vector<uint8_t> m_decompress_buf;
    z_stream m_zstream;

    void ProcessNewGuild(Guild &guild);

    void HandleGatewayMessageRaw(std::string str);
    void HandleGatewayMessage(std::string str);
    void HandleGatewayReady(const GatewayMessage &msg);
    void HandleGatewayMessageCreate(const GatewayMessage &msg);
    void HandleGatewayMessageDelete(const GatewayMessage &msg);
    void HandleGatewayMessageUpdate(const GatewayMessage &msg);
    void HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg);
    void HandleGatewayGuildCreate(const GatewayMessage &msg);
    void HandleGatewayGuildDelete(const GatewayMessage &msg);
    void HandleGatewayMessageDeleteBulk(const GatewayMessage &msg);
    void HeartbeatThread();
    void SendIdentify();

    bool CheckCode(const cpr::Response &r);

    std::string m_token;

    void AddMessageToChannel(Snowflake msg_id, Snowflake channel_id);
    std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_chan_to_message_map;

    void AddUserToGuild(Snowflake user_id, Snowflake guild_id);
    std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_guild_to_users;

    User m_user_data;
    UserSettings m_user_settings;

    Store m_store;
    HTTPClient m_http;
    Websocket m_websocket;
    std::atomic<bool> m_client_connected = false;
    std::atomic<bool> m_ready_received = false;

    std::unordered_map<std::string, GatewayEvent> m_event_map;
    void LoadEventMap();

    std::thread m_heartbeat_thread;
    std::atomic<int> m_last_sequence = -1;
    std::atomic<int> m_heartbeat_msec = 0;
    HeartbeatWaiter m_heartbeat_waiter;
    std::atomic<bool> m_heartbeat_acked = true;

    mutable std::mutex m_msg_mutex;
    Glib::Dispatcher m_msg_dispatch;
    std::queue<std::string> m_msg_queue;
    void MessageDispatch();

    // signals
public:
    typedef sigc::signal<void> type_signal_gateway_ready;
    typedef sigc::signal<void> type_signal_channel_list_refresh;
    typedef sigc::signal<void, Snowflake> type_signal_message_create;
    typedef sigc::signal<void, Snowflake, Snowflake> type_signal_message_delete;
    typedef sigc::signal<void, Snowflake, Snowflake> type_signal_message_update;
    typedef sigc::signal<void, Snowflake> type_signal_guild_member_list_update;
    typedef sigc::signal<void, Snowflake> type_signal_guild_create;
    typedef sigc::signal<void, Snowflake> type_signal_guild_delete;

    type_signal_gateway_ready signal_gateway_ready();
    type_signal_channel_list_refresh signal_channel_list_refresh();
    type_signal_message_create signal_message_create();
    type_signal_message_delete signal_message_delete();
    type_signal_message_update signal_message_update();
    type_signal_guild_member_list_update signal_guild_member_list_update();
    type_signal_guild_create signal_guild_create();
    type_signal_guild_delete signal_guild_delete();

protected:
    type_signal_gateway_ready m_signal_gateway_ready;
    type_signal_channel_list_refresh m_signal_channel_list_refresh;
    type_signal_message_create m_signal_message_create;
    type_signal_message_delete m_signal_message_delete;
    type_signal_message_update m_signal_message_update;
    type_signal_guild_member_list_update m_signal_guild_member_list_update;
    type_signal_guild_create m_signal_guild_create;
    type_signal_guild_delete m_signal_guild_delete;
};