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 inline constexpr uint16_t webSocketMaxPayloadLengthLarge{ 65535u };
44 inline constexpr uint8_t webSocketPayloadLengthMagicLarge{ 126u };
45 inline constexpr uint8_t webSocketPayloadLengthMagicHuge{ 127u };
46 inline constexpr uint8_t maxHeaderSize{ sizeof(uint64_t) + 2u };
47 inline constexpr uint8_t webSocketMaxPayloadLengthSmall{ 125u };
48 inline 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 = 1 << 0,///< Unset.
58 Normal_Close = 1 << 1,///< Normal close.
59 Unknown_Error = 1 << 2,///< We're not sure what went wrong. try reconnecting?
60 Unknown_Opcode = 1 << 3,///< You sent an invalid gateway opcode or an invalid payload for an opcode. don't do that!
61 Decode_Error = 1 << 4,///< You sent an invalid payload to us. don't do that!
62 Not_Authenticated = 1 << 5,///< You sent us a payload prior to identifying.
63 Authentication_Failed = 1 << 6,///< The account token sent with your identify payload is incorrect.
64 Already_Authenticated = 1 << 7,///< You sent more than one identify payload. don't do that!
65 Invalid_Seq = 1 << 8,///< the sequence sent when resuming the session was invalid. reconnect and start a new session.
66 Rate_Limited = 1 << 9,///< Woah nelly! you're sending payloads to us too quickly. slow it down! you will be disconnected on receiving this.
67 Session_Timed = 1 << 10,///< Your session timed out. reconnect and start a new one.
68 Invalid_Shard = 1 << 11,///< You sent us an invalid shard when identifying.
69 Sharding_Required = 1 << 12,///< The session would have handled too many guilds - you are required to shard your connection in order to connect.
70 Invalid_API_Version = 1 << 13,///< You sent an invalid version for the gateway.
71 Invalid_Intent = 1 << 14,///< You sent an invalid intent for a gateway intent. you may have incorrectly calculated the bitwise value.
73 1 << 15,///< 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 approved for.
74 We_Do_Reconnect =
77 };
78
79 inline static unordered_map<int32_t, websocket_close_code> mappingValues{ { 0, websocket_close_code::unset }, { 1000, websocket_close_code::Normal_Close },
85
86 inline static unordered_map<websocket_close_code, jsonifier::string_view> outputErrorValues{ {
88 "we're not sure what went wrong.",
89 },
90 { websocket_close_code::Unknown_Opcode, "you sent an invalid gateway opcode or an invalid payload for an opcode. don't do that!" },
91 { websocket_close_code::Decode_Error, "you sent an invalid payload to discord. don't do that!" },
92 { websocket_close_code::Not_Authenticated, "you sent us a payload prior to identifying." },
93 { websocket_close_code::Authentication_Failed, "the account token sent with your identify payload is incorrect." },
94 { websocket_close_code::Already_Authenticated, "you sent more than one identify payload. don't do that!" },
95 { websocket_close_code::Invalid_Seq, "the sequence sent when resuming the session was invalid. reconnect and start a new session." },
96 { 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." },
97 { websocket_close_code::Session_Timed, "your session timed out. reconnect and start a new one." },
98 { websocket_close_code::Invalid_Shard, "you sent us an invalid shard when identifying." },
99 { websocket_close_code::Sharding_Required, "the session would have handled too many guilds - you are required to shard your connection in order to connect." },
100 { websocket_close_code::Invalid_API_Version, "you sent an invalid version for the gateway." },
101 { websocket_close_code::Invalid_Intent, "you sent an invalid intent for a gateway intent. you may have incorrectly calculated the bitwise value." },
103 "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 "
104 "approved for." } };
105
106 websocket_close_code value{};
107
108 inline websocket_close& operator=(uint16_t valueNew) {
109 value = static_cast<websocket_close_code>(valueNew);
110 return *this;
111 };
112
113 inline websocket_close(uint16_t valueNew) {
114 *this = valueNew;
115 };
116
117 inline operator jsonifier::string_view() {
118 return websocket_close::outputErrorValues[mappingValues[static_cast<uint16_t>(value)]];
119 }
120
121 inline operator bool() {
122 return static_cast<std::underlying_type_t<decltype(value)>>(value) & static_cast<std::underlying_type_t<decltype(value)>>(websocket_close_code::We_Do_Reconnect);
123 }
124 };
125
126 class DiscordCoreAPI_Dll event_converter {
127 public:
128 event_converter(jsonifier::string eventNew);
129
130 operator uint64_t();
131
132 protected:
133 jsonifier::string eventValue{};
134 };
135
136 /// @brief For the opcodes that could be sent/received via discord's websockets.
138 dispatch = 0,///< An event was dispatched.
139 heartbeat = 1,///< Fired periodically by the client to keep the connection alive.
140 identify = 2,///< Starts a new session during the initial handshake.
141 Presence_Update = 3,///< Update the client's presence.
142 Voice_State_Update = 4,///< Used to join/leave or move between voice channels.
143 resume = 6,///< Resume a previous session that was disconnected.
144 reconnect = 7,///< You should attempt to reconnect and resume immediately.
145 Request_Guild_Members = 8,///< Request information about offline guild members in a large guild.
146 Invalid_Session = 9,/// the session has been invalidated. you should reconnect and identify/resume accordingly.
147 hello = 10,///< Sent immediately after connecting, contains the heartbeat_interval to use.
148 Heartbeat_ACK = 11,///<sent in response to receiving a heartbeat to acknowledge that it has been received.
149 };
150
151 class websocket_core;
152
153 class DiscordCoreAPI_Dll websocket_tcpconnection : public tcp_connection<websocket_tcpconnection> {
154 public:
155 friend class websocket_core;
156
157 inline websocket_tcpconnection() = default;
158
159 inline websocket_tcpconnection& operator=(websocket_tcpconnection&& other) = default;
160 inline websocket_tcpconnection(websocket_tcpconnection&& other) = default;
161
162 websocket_tcpconnection(const jsonifier::string& baseUrlNew, uint16_t portNew, websocket_core* ptrNew);
163
164 void handleBuffer() override;
165
166 protected:
167 websocket_core* ptr{};
168 };
169
170 enum class websocket_type { normal = 0, voice = 1 };
171
172 enum class websocket_state { connecting = 0, upgrading = 1, Collecting_Hello = 2, Sending_Identify = 3, authenticated = 4, disconnected = 5 };
173
174 class DiscordCoreAPI_Dll websocket_core : public etf_parser {
175 public:
177 friend class websocket_tcpconnection;
178
179 inline websocket_core() = default;
180
181 websocket_core& operator=(websocket_core&& data) noexcept;
182 websocket_core(websocket_core&& data) noexcept;
183
184 websocket_core(config_manager* configManagerNew, websocket_type typeOfWebSocketNew);
185
186 template<typename value_type> void createHeader(jsonifier::string_base<value_type>& outBuffer, websocket_op_code opCode) {
187 int64_t originalSize{ static_cast<int64_t>(outBuffer.size()) };
188 outBuffer.insert(outBuffer.begin(), static_cast<value_type>(static_cast<uint8_t>(opCode) | webSocketMaskBit));
189
190 int64_t indexCount{};
191 if (originalSize <= webSocketMaxPayloadLengthSmall) {
192 outBuffer.insert(outBuffer.begin() + 1, static_cast<value_type>(originalSize));
193 indexCount = 0;
194 } else if (originalSize <= webSocketMaxPayloadLengthLarge) {
195 outBuffer.insert(outBuffer.begin() + 1, static_cast<value_type>(webSocketPayloadLengthMagicLarge));
196 indexCount = 2;
197 } else {
198 outBuffer.insert(outBuffer.begin() + 1, static_cast<value_type>(webSocketPayloadLengthMagicHuge));
199 indexCount = 8;
200 }
201 for (int64_t x = indexCount - 1; x >= 0; x--) {
202 outBuffer.insert(outBuffer.begin() + 1 + indexCount - x, static_cast<value_type>(originalSize >> (x * 8)));
203 }
204
205 outBuffer.at(1) |= webSocketMaskBit;
206 outBuffer.insert(outBuffer.begin() + 2 + indexCount, 0);
207 outBuffer.insert(outBuffer.begin() + 3 + indexCount, 0);
208 outBuffer.insert(outBuffer.begin() + 4 + indexCount, 0);
209 outBuffer.insert(outBuffer.begin() + 5 + indexCount, 0);
210 }
211
212 bool connect(const jsonifier::string& baseUrlNew, jsonifier::string_view relativePath, const uint16_t portNew);
213
214 virtual bool onMessageReceived(jsonifier::string_view_base<uint8_t> message) = 0;
215
216 bool sendMessage(jsonifier::string_base<uint8_t>& dataToSend, bool priority);
217
218 bool checkForAndSendHeartBeat(bool = false);
219
220 void parseConnectionHeaders();
221
222 virtual void onClosed() = 0;
223
224 bool areWeConnected();
225
226 bool parseMessage();
227
228 void disconnect();
229
230 virtual ~websocket_core() = default;
231
232 protected:
233 stop_watch<milliseconds> heartBeatStopWatch{ 20000ms };
234 jsonifier::string_base<uint8_t> currentMessage{};
235 std::atomic<websocket_state> currentState{};
236 bool haveWeReceivedHeartbeatAck{ true };
237 std::atomic_bool areWeCollectingData{};
238 websocket_tcpconnection tcpConnection{};
239 uint32_t maxReconnectTries{ 10 };
240 uint32_t currentReconnectTries{};
241 std::array<uint64_t, 2> shard{};
242 config_manager* configManager{};
243 uint32_t lastNumberReceived{};
244 websocket_op_code dataOpCode{};
245 std::mutex accessMutex{};
246 bool areWeHeartBeating{};
247 websocket_type wsType{};
248 bool areWeResuming{};
249 };
250
251 /// @brief A websocket client, for communication via a tcp-connection.
252 class websocket_client : public websocket_core {
253 public:
256 friend class tcp_connection<websocket_tcpconnection>;
259 friend class discord_core_api::bot_user;
260 friend class base_socket_agent;
261 friend class sound_cloud_api;
262 friend class websocket_core;
263 friend class you_tube_api;
264
265 inline websocket_client() = default;
266
267 inline websocket_client& operator=(websocket_client&&) = default;
268 inline websocket_client(websocket_client&&) = default;
269
270 websocket_client(uint64_t currentShardNew, std::atomic_bool* doWeQuitNew);
271
272 void getVoiceConnectionData(const voice_connect_init_data& doWeCollect);
273
274 bool onMessageReceived(jsonifier::string_view_base<uint8_t> message) override;
275
276 void disconnect();
277
278 void onClosed() override;
279
280 virtual ~websocket_client();
281
282 protected:
283 unordered_map<uint64_t, unbounded_message_block<voice_connection_data>*> voiceConnectionDataBufferMap{};
284 voice_connection_data voiceConnectionData{};
285 jsonifier::string resumeUrl{};
286 jsonifier::string sessionId{};
287 std::atomic_bool* doWeQuit{};
288 bool serverUpdateCollected{};
289 bool stateUpdateCollected{};
290 snowflake userId{};
291 };
292
293 class base_socket_agent {
294 public:
296 friend class discord_core_api::bot_user;
297
298 base_socket_agent(std::atomic_bool* doWeQuitNew);
299
300 void connect(websocket_client& value);
301
302 ~base_socket_agent();
303
304 protected:
305 unordered_map<uint64_t, websocket_client> shardMap{};
306 std::deque<connection_package> connections{};
307 std::atomic_bool* doWeQuit{};
308 std::jthread taskThread{};
309
310 void run(std::stop_token);
311 };
312
313 }// namespace
314}
websocket_op_codes
For the opcodes that could be sent/received via discord's websockets.
@ Request_Guild_Members
Request information about offline guild members in a large guild.
@ reconnect
You should attempt to reconnect and resume immediately.
@ Voice_State_Update
Used to join/leave or move between voice channels.
A type of user_data, to represent the bot and some of its associated endpoints.
discord_core_client - the main class for this library.
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...
@ Decode_Error
You sent an invalid payload to us. don't do that!
@ 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...
@ Unknown_Error
We're not sure what went wrong. try reconnecting?
@ 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.
A class representing a snowflake identifier with various operations.
Definition: Base.hpp:680
voice_connection class - represents the connection to a given voice channel_data.
@ connect
Allows for joining of a voice channel.
The main namespace for the forward-facing interfaces.
@ hello
Time to wait between sending heartbeats in milliseconds.
@ Heartbeat_ACK
Sent to acknowledge a received client heartbeat.
@ identify
Begin a voice websocket connection.
@ Sending_Identify
Sending the identify payload.
@ Collecting_Hello
collecting the client hello.
Data that is received as part of a voice server update event.
Data that is received as part of a voice state update event.
For connecting to a voice-channel. "streamInfo" is used when a SOCKET is created to connect this bot ...
Definition: Utilities.hpp:403