summaryrefslogtreecommitdiff
path: root/discord/discord.hpp
blob: 692d57b1a7cd37b9c3ae3fe8c97b4af1282b2d8e (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
#pragma once
#include "websocket.hpp"
#include <nlohmann/json.hpp>
#include <thread>

enum class GatewayOp : int {
    Heartbeat = 1,
    Hello = 10,
    HeartbeatAck = 11,
};

struct GatewayMessage {
    GatewayOp Opcode;
    nlohmann::json Data;
    std::string Type;

    friend void from_json(const nlohmann::json &j, GatewayMessage &m);
};

struct HelloMessageData {
    int HeartbeatInterval;

    friend void from_json(const nlohmann::json &j, HelloMessageData &m);
};

struct HeartbeatMessage : GatewayMessage {
    int Sequence;

    friend void to_json(nlohmann::json &j, const HeartbeatMessage &m);
};

// 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 DiscordClient {
public:
    static const constexpr char *DiscordGateway = "wss://gateway.discord.gg/?v=6&encoding=json";
    static const constexpr char *DiscordAPI = "https://discord.com/api";

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

private:
    void HandleGatewayMessage(nlohmann::json msg);
    void HeartbeatThread();

    Websocket m_websocket;
    bool m_client_connected = false;

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