DiscordCoreAPI
A Discord bot library written in C++, with custom asynchronous coroutines.
Loading...
Searching...
No Matches
WebSocketClient.hpp
Go to the documentation of this file.
1/*
2 MIT License
3
4 DiscordCoreAPI, A bot library for Discord, written in C++, and featuring explicit multithreading through the usage of custom, asynchronous C++ CoRoutines.
5
6 Copyright 2022, 2023 Chris M. (RealTimeChris)
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25*/
26/// WebsocketClient.hpp - Header for the webSocket related classes and
27/// structs. may 13, 2021 Chris M.
28/// https://discordcoreapi.com
29/// \file WebsocketClient.hpp
30#pragma once
31
37#include <thread>
38
39namespace discord_core_api {
40
41 namespace discord_core_internal {
42
43 constexpr uint16_t webSocketMaxPayloadLengthLarge{ 65535u };
44 constexpr uint8_t webSocketPayloadLengthMagicLarge{ 126u };
45 constexpr uint8_t webSocketPayloadLengthMagicHuge{ 127u };
46 constexpr uint8_t maxHeaderSize{ sizeof(uint64_t) + 2u };
47 constexpr uint8_t webSocketMaxPayloadLengthSmall{ 125u };
48 constexpr uint8_t webSocketMaskBit{ (1u << 7u) };
49
50 enum class websocket_op_code : uint8_t { Op_Continuation = 0x00, Op_Text = 0x01, Op_Binary = 0x02, Op_Close = 0x08, Op_Ping = 0x09, Op_Pong = 0x0a };
51
52 /// @brief Websocket close codes.
54 public:
55 /// @brief Websocket close codes.
56 enum class websocket_close_code : uint16_t {
57 Unset = 0,///< Unset.
58 Normal_Close = 1000,///< Normal close.
59 Unknown_Error = 4000,///< We're not sure what went wrong. try reconnecting?
60 Unknown_Opcode = 4001,///< You sent an invalid gateway opcode or an invalid payload for an opcode. don't do that!
61 Decode_Error = 4002,///< You sent an invalid payload to us. don't do that!
62 Not_Authenticated = 4003,///< You sent us a payload prior to identifying.
63 Authentication_Failed = 4004,///< The account token sent with your identify payload is incorrect.
64 Already_Authenticated = 4005,///< You sent more than one identify payload. don't do that!
65 Invalid_Seq = 4007,///< the sequence sent when resuming the session was invalid. reconnect and start a new session.
66 Rate_Limited = 4008,///< Woah nelly! you're sending payloads to us too quickly. slow it down! you will be disconnected on receiving this.
67 Session_Timed = 4009,///< Your session timed out. reconnect and start a new one.
68 Invalid_Shard = 4010,///< You sent us an invalid shard when identifying.
69 Sharding_Required = 4011,///< The session would have handled too many guilds - you are required to shard your connection in order to connect.
70 Invalid_API_Version = 4012,///< You sent an invalid version for the gateway.
71 Invalid_Intent = 4013,///< You sent an invalid intent for a gateway intent. you may have incorrectly calculated the bitwise value.
72 Disallowed_Intent = 4014,///< You sent a disallowed intent for a gateway intent. you may have tried to specify an intent that you have not enabled.
73 };
74
75 DCA_INLINE static unordered_map<websocket_close_code, jsonifier::string> outputErrorValues{ {
77 "we're not sure what went wrong.",
78 },
79 { websocket_close_code::Unknown_Opcode, "you sent an invalid gateway opcode or an invalid payload for an opcode. don't do that!" },
80 { websocket_close_code::Decode_Error, "you sent an invalid payload to discord. don't do that!" },
81 { websocket_close_code::Not_Authenticated, "you sent us a payload prior to identifying." },
82 { websocket_close_code::Authentication_Failed, "The account token sent with your identify payload is incorrect." },
83 { websocket_close_code::Already_Authenticated, "you sent more than one identify payload. don't do that!" },
84 { websocket_close_code::Invalid_Seq, "The sequence sent when resuming the session was invalid. reconnect and start a new session." },
85 { websocket_close_code::Rate_Limited, "woah nelly! you're sending payloads to us too quickly. slow it down! you will be disconnected on receiving this." },
86 { websocket_close_code::Session_Timed, "your session timed out. reconnect and start a new one." },
87 { websocket_close_code::Invalid_Shard, "you sent us an invalid shard when identifying." },
88 { websocket_close_code::Sharding_Required, "The session would have handled too many guilds - you are required to shard your connection in order to connect." },
89 { websocket_close_code::Invalid_API_Version, "you sent an invalid version for the gateway." },
90 { websocket_close_code::Invalid_Intent, "you sent an invalid intent for a gateway intent. you may have incorrectly calculated the bitwise value." },
92 "you sent a disallowed intent for a gateway intent. you may have tried to specify an intent that you have not enabled or are not "
93 "approved for." } };
94
96
97 DCA_INLINE websocket_close& operator=(uint16_t valueNew) {
98 value = static_cast<websocket_close_code>(valueNew);
99 return *this;
100 };
101
102 DCA_INLINE websocket_close(uint16_t valueNew) {
103 *this = valueNew;
104 };
105
106 DCA_INLINE operator jsonifier::string_view() {
107 return websocket_close::outputErrorValues[static_cast<websocket_close_code>(value)];
108 }
109 };
110
111 class DiscordCoreAPI_Dll event_converter {
112 public:
113 event_converter(const jsonifier::string& eventNew);
114
115 operator uint64_t();
116
117 protected:
118 jsonifier::string_view eventValue{};
119 };
120
121 /// @brief For the opcodes that could be sent/received via discord's websockets.
123 dispatch = 0,///< An event was dispatched.
124 heartbeat = 1,///< Fired periodically by the client to keep the connection alive.
125 identify = 2,///< Starts a new session during the initial handshake.
126 Presence_Update = 3,///< Update the client's presence.
127 Voice_State_Update = 4,///< Used to join/leave or move between voice channels.
128 resume = 6,///< Resume a previous session that was disconnected.
129 reconnect = 7,///< You should attempt to reconnect and resume immediately.
130 Request_Guild_Members = 8,///< Request information about offline guild members in a large guild.
131 Invalid_Session = 9,/// the session has been invalidated. you should reconnect and identify/resume accordingly.
132 hello = 10,///< Sent immediately after connecting, contains the heartbeat_interval to use.
133 Heartbeat_ACK = 11,///<sent in response to receiving a heartbeat to acknowledge that it has been received.
134 };
135
136 class websocket_core;
137
138 class DiscordCoreAPI_Dll websocket_tcpconnection : public tcp_connection<websocket_tcpconnection> {
139 public:
140 friend class websocket_core;
141
142 DCA_INLINE websocket_tcpconnection() = default;
143
144 DCA_INLINE websocket_tcpconnection& operator=(websocket_tcpconnection&& other) = default;
145 DCA_INLINE websocket_tcpconnection(websocket_tcpconnection&& other) = default;
146
147 websocket_tcpconnection(const jsonifier::string& baseUrlNew, uint16_t portNew, websocket_core* ptrNew);
148
149 void handleBuffer() override;
150
151 protected:
152 websocket_core* ptr{};
153 };
154
155 enum class websocket_type { normal = 0, voice = 1 };
156
157 enum class websocket_state { connecting = 0, upgrading = 1, Collecting_Hello = 2, Sending_Identify = 3, authenticated = 4, disconnected = 5 };
158
159 class DiscordCoreAPI_Dll websocket_core {
160 public:
161 friend class discord_core_api::voice_connection;
162 friend class websocket_tcpconnection;
163
164 DCA_INLINE websocket_core() = default;
165
166 websocket_core& operator=(websocket_core&& data) noexcept;
167 websocket_core(websocket_core&& data) noexcept;
168
169 websocket_core(config_manager* configManagerNew, websocket_type typeOfWebSocketNew);
170
171 template<typename value_type> void createHeader(jsonifier::string_base<value_type>& outBuffer, websocket_op_code opCode) {
172 int64_t originalSize{ static_cast<int64_t>(outBuffer.size()) };
173 outBuffer.insert(outBuffer.begin(), static_cast<value_type>(static_cast<uint8_t>(opCode) | webSocketMaskBit));
174
175 int64_t indexCount{};
176 if (originalSize <= webSocketMaxPayloadLengthSmall) {
177 outBuffer.insert(outBuffer.begin() + 1, static_cast<value_type>(originalSize));
178 indexCount = 0;
179 } else if (originalSize <= webSocketMaxPayloadLengthLarge) {
180 outBuffer.insert(outBuffer.begin() + 1, static_cast<value_type>(webSocketPayloadLengthMagicLarge));
181 indexCount = 2;
182 } else {
183 outBuffer.insert(outBuffer.begin() + 1, static_cast<value_type>(webSocketPayloadLengthMagicHuge));
184 indexCount = 8;
185 }
186 for (int64_t x = indexCount - 1; x >= 0; x--) {
187 outBuffer.insert(outBuffer.begin() + 1 + indexCount - x, static_cast<value_type>(originalSize >> (x * 8)));
188 }
189
190 outBuffer.at(1) |= webSocketMaskBit;
191 outBuffer.insert(outBuffer.begin() + 2 + indexCount, 0);
192 outBuffer.insert(outBuffer.begin() + 3 + indexCount, 0);
193 outBuffer.insert(outBuffer.begin() + 4 + indexCount, 0);
194 outBuffer.insert(outBuffer.begin() + 5 + indexCount, 0);
195 }
196
197 bool connect(const jsonifier::string& baseUrlNew, jsonifier::string_view relativePath, const uint16_t portNew);
198
199 virtual bool onMessageReceived(jsonifier::string_view_base<uint8_t> message) = 0;
200
201 bool sendMessage(jsonifier::string_base<uint8_t>& dataToSend, bool priority);
202
203 bool checkForAndSendHeartBeat(bool = false);
204
205 void parseConnectionHeaders();
206
207 virtual void onClosed() = 0;
208
209 bool areWeConnected();
210
211 bool parseMessage();
212
213 void disconnect();
214
215 virtual ~websocket_core() = default;
216
217 protected:
218 stop_watch<milliseconds> heartBeatStopWatch{ 20000ms };
219 jsonifier::string_base<uint8_t> currentMessage{};
220 std::atomic<websocket_state> currentState{};
221 bool haveWeReceivedHeartbeatAck{ true };
222 std::atomic_bool areWeCollectingData{};
223 websocket_tcpconnection tcpConnection{};
224 uint32_t maxReconnectTries{ 10 };
225 uint32_t currentReconnectTries{};
226 std::array<uint64_t, 2> shard{};
227 config_manager* configManager{};
228 uint32_t lastNumberReceived{};
229 websocket_op_code dataOpCode{};
230 std::mutex accessMutex{};
231 bool areWeHeartBeating{};
232 websocket_type wsType{};
233 bool areWeResuming{};
234 };
235
236 /// @brief A websocket client, for communication via a tcp-connection.
237 class websocket_client : public websocket_core {
238 public:
239 friend struct discord_core_api::on_voice_server_update_data;
240 friend struct discord_core_api::on_voice_state_update_data;
241 friend class tcp_connection<websocket_tcpconnection>;
242 friend class discord_core_api::discord_core_client;
243 friend class discord_core_api::voice_connection;
244 friend class discord_core_api::bot_user;
245 friend class base_socket_agent;
246 friend class sound_cloud_api;
247 friend class websocket_core;
248 friend class you_tube_api;
249
250 DCA_INLINE websocket_client() = default;
251
252 DCA_INLINE websocket_client& operator=(websocket_client&&) = default;
253 DCA_INLINE websocket_client(websocket_client&&) = default;
254
255 websocket_client(uint64_t currentShardNew, std::atomic_bool* doWeQuitNew);
256
257 void getVoiceConnectionData(const voice_connect_init_data& doWeCollect);
258
259 bool onMessageReceived(jsonifier::string_view_base<uint8_t> message) override;
260
261 void disconnect();
262
263 void onClosed() override;
264
265 virtual ~websocket_client();
266
267 protected:
268 unordered_map<uint64_t, unbounded_message_block<voice_connection_data>*> voiceConnectionDataBufferMap{};
269 voice_connection_data voiceConnectionData{};
270 jsonifier::string resumeUrl{};
271 jsonifier::string sessionId{};
272 std::atomic_bool* doWeQuit{};
273 bool serverUpdateCollected{};
274 bool stateUpdateCollected{};
275 snowflake userId{};
276 };
277
278 class base_socket_agent {
279 public:
280 friend class discord_core_api::discord_core_client;
281 friend class discord_core_api::bot_user;
282
283 base_socket_agent(std::atomic_bool* doWeQuitNew);
284
285 void connect(websocket_client& value);
286
287 ~base_socket_agent();
288
289 protected:
290 unordered_map<uint64_t, websocket_client> shardMap{};
291 std::deque<connection_package> connections{};
292 std::atomic_bool* doWeQuit{};
293 std::jthread taskThread{};
294
295 void run(std::stop_token);
296 };
297
298 }// namespace
299}
websocket_op_codes
For the opcodes that could be sent/received via discord's websockets.
@ heartbeat
Fired periodically by the client to keep the connection alive.
@ hello
the session has been invalidated. you should reconnect and identify/resume accordingly.
@ resume
Resume a previous session that was disconnected.
@ Request_Guild_Members
Request information about offline guild members in a large guild.
@ Heartbeat_ACK
sent in response to receiving a heartbeat to acknowledge that it has been received.
@ reconnect
You should attempt to reconnect and resume immediately.
@ identify
Starts a new session during the initial handshake.
@ Voice_State_Update
Used to join/leave or move between voice channels.
A websocket client, for communication via a tcp-connection.
@ Sharding_Required
The session would have handled too many guilds - you are required to shard your connection in order t...
@ Invalid_Intent
You sent an invalid intent for a gateway intent. you may have incorrectly calculated the bitwise valu...
@ Already_Authenticated
You sent more than one identify payload. don't do that!
@ Invalid_Seq
the sequence sent when resuming the session was invalid. reconnect and start a new session.
@ Authentication_Failed
The account token sent with your identify payload is incorrect.
@ Unknown_Opcode
You sent an invalid gateway opcode or an invalid payload for an opcode. don't do that!
@ Disallowed_Intent
You sent a disallowed intent for a gateway intent. you may have tried to specify an intent that you h...
@ Rate_Limited
Woah nelly! you're sending payloads to us too quickly. slow it down! you will be disconnected on rece...
@ Session_Timed
Your session timed out. reconnect and start a new one.
@ connect
Allows for joining of a voice channel.
The main namespace for the forward-facing interfaces.