├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── find └── FindJansson.cmake ├── include └── DisC │ ├── DisC.h │ ├── DisC_errors.h │ ├── DisC_gateway.h │ ├── DisC_object.h │ ├── DisC_rest.h │ ├── DisC_types.h │ └── DisC_util.h └── src ├── DisC.c ├── DisC_errors.c ├── DisC_gateway.c ├── DisC_object.c ├── DisC_rest.c └── DisC_util.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.a 2 | CMakeFiles/ 3 | CMakeCache.txt 4 | *.cmake 5 | # Now that cmake is in place this is a good thing 6 | Makefile 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMake stuff for DisC by ~ironlake 2 | 3 | # CMake set and package finding stuff 4 | cmake_minimum_required(VERSION 3.6 ) 5 | project(DisC ) 6 | set(DISC_MAJ 0 ) 7 | set(DISC_MIN 1 ) 8 | set(DISC_VER ${DISC_MAJ}.${DISC_MIN} ) 9 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/find/" ) 10 | add_definitions( -DDISC_VER="${DISC_VER}" ) 11 | find_package(OpenSSL REQUIRED ) 12 | find_package(Jansson REQUIRED ) 13 | include_directories(AFTER ${JANSSON_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ) 14 | include_directories(BEFORE include/DisC ) 15 | 16 | # library 17 | ADD_LIBRARY( DisC STATIC src/DisC_errors.c src/DisC.c src/DisC_rest.c src/DisC_util.c src/DisC_gateway.c src/DisC_object.c ) 18 | 19 | 20 | install(TARGETS DisC DESTINATION lib) 21 | install(DIRECTORY include/DisC DESTINATION include) 22 | # test executable(s) (tbd) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DisC 2 | A discord library for C. You can use it with C++, but it doesn't have any nifty objects. DisC is planned to work on all platforms that support mbedTLS (but for now OpenSSL), which is basically anything that can connect to the internet. 3 | 4 | ## Build instructions 5 | * `mkdir build` 6 | * `cd build` 7 | * `cmake ..` 8 | * `make` 9 | * `(sudo) make install` 10 | 11 | ## Status 12 | Very broken and in early stages. I plan on completing this. 13 | Also, I'll be changing the name from DISC to DisC in the future. 14 | 15 | ## TODO 16 | * Add all REST functions 17 | * Add ratelimiting 18 | * Proper gateway handling 19 | * Add everything to work with the gateway and use callbacks 20 | * Add better thread support 21 | * Rewrite at 1.0 22 | 23 | ## Plans at version 1.0 24 | * At version 1.0, all of the REST and gateway functions are implemented. 25 | * Voice connection comes after 1.0 26 | * Expect the api to change drastically up until 1.0 27 | * Use my separate websocket/REST library (I don't want the REST and websocket client parts to be DisC only) 28 | 29 | ## Thread saftey 30 | currently, DisC is single threaded and doesnt support threading as far as I know. I will add this in the future. 31 | 32 | ## Libraries Needed 33 | You're gonna need Jansson and OpenSSL (in the future, mbedTLS). 34 | 35 | ## Want to test your bot? 36 | This is a guild where you can invite your bots and talk to me about features you want to add or bugs that need to be squashed. 37 | 38 | [![Discord ](https://discordapp.com/api/guilds/263823960116953088/embed.png?style=banner1)](https://discord.gg/nSa7n8v) 39 | -------------------------------------------------------------------------------- /find/FindJansson.cmake: -------------------------------------------------------------------------------- 1 | # Try to find Jansson 2 | # Once done this will define 3 | # JANSSON_FOUND 4 | # JANSSON_INCLUDE_DIRS 5 | # JANSSON_LIBRARIES 6 | # JANSSON_DEFINITIONS 7 | 8 | 9 | find_package(PkgConfig) 10 | pkg_check_modules(PC_JANSSON QUIET libjansson) 11 | set(JANSSON_DEFINITIONS ${PC_JANSSON_CFLAGS_OTHER}) 12 | 13 | find_path(JANSSON_INCLUDE_DIR jansson.h HINTS ${PC_JANSSON_INCLUDEDIR} ${PC_JANSSON_INCLUDE_DIRS} PATH_SUFFIXES jansson) 14 | 15 | find_library(JANSSON_LIBRARY NAMES jansson libjansson HINTS ${PC_JANSSON_LIBDIR} ${PC_JANSSON_LIBRARY_DIRS}) 16 | 17 | set(JANSSON_LIBRARIES ${JANSSON_LIBRARY}) 18 | set(JANSSON_INCLUDE_DIRS ${JANSSON_INCLUDE_DIR}) 19 | 20 | include(FindPackageHandleStandardArgs) 21 | find_package_handle_standard_args(LibJansson DEFAULT_MSG JANSSON_LIBRARY JANSSON_INCLUDE_DIR) 22 | 23 | mark_as_advanced(JANSSON_INCLUDE_DIR JANSSON_LIBRARY) 24 | -------------------------------------------------------------------------------- /include/DisC/DisC.h: -------------------------------------------------------------------------------- 1 | #ifndef DISC_H__ 2 | #define DISC_H__ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "DisC_types.h" 9 | #include "DisC_errors.h" 10 | #include "DisC_util.h" 11 | #include "DisC_rest.h" 12 | #include "DisC_object.h" 13 | #include "DisC_gateway.h" 14 | 15 | 16 | short DisC_InitLibrary(); 17 | 18 | short DisC_DestroyLibrary(); 19 | 20 | short DisC_CreateSession(DisC_session_t *session, DisC_callbacks_t *callbacks); 21 | 22 | short DisC_DesroySession(DisC_session_t *session); 23 | 24 | short DisC_StartAllSessions(); 25 | 26 | 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif 31 | 32 | #endif /* DISC_H__*/ 33 | -------------------------------------------------------------------------------- /include/DisC/DisC_errors.h: -------------------------------------------------------------------------------- 1 | #ifndef ERRORS_H__ 2 | #define ERRORS_H__ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "DisC_types.h" 9 | 10 | enum DisC_Errors 11 | { 12 | DISC_ERROR_NONE, 13 | DISC_ERROR_GENERIC, 14 | DISC_ERROR_PERMISSIONS, 15 | DISC_ERROR_API_ERROR, 16 | DISC_ERROR_CONNECTION, 17 | DISC_ERROR_OBJECT_NONEXISTANT, 18 | DISC_ERROR_CONTENT_TOO_LARGE, 19 | DISC_ERROR_MEMORY, 20 | }; 21 | 22 | void DisC_PrintError(DisC_session_t *session); 23 | 24 | const char *DisC_GetSessionErrorString(DisC_session_t *session); 25 | 26 | const char *DisC_GetErrorString(unsigned short error); 27 | 28 | void DisC_AddError(DisC_session_t *session, int error); 29 | 30 | #ifdef __cplusplus 31 | } 32 | #endif 33 | 34 | #endif /* ERRORS_H__*/ 35 | -------------------------------------------------------------------------------- /include/DisC/DisC_gateway.h: -------------------------------------------------------------------------------- 1 | #ifndef GATEWAY_H__ 2 | #define GATEWAY_H__ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | short DisC_gateway_InitSession(DisC_session_t *session, DisC_callbacks_t *callbacks); 9 | 10 | short DisC_gateway_DestroySession(DisC_session_t *session); 11 | 12 | short DisC_gateway_ListenAndManage(DisC_session_t *session); 13 | 14 | short DisC_gateway_SendEvent(DisC_session_t *session, short OP, unsigned char *data, unsigned long dataLen); 15 | 16 | short DisC_gateway_BlockAndListenForEvent(DisC_session_t *session, DisC_gateway_payload_t **payload); 17 | 18 | short DisC_gateway_Identify(DisC_session_t *session, short large_threshold, short shardnum, short shardmax, DisC_gateway_status_update_t *status_update); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif /* GATEWAY_H__*/ 25 | -------------------------------------------------------------------------------- /include/DisC/DisC_object.h: -------------------------------------------------------------------------------- 1 | #ifndef OBJECT_H__ 2 | #define OBJECT_H__ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | DisC_gateway_payload_t *DisC_object_GenerateGatewayPayload(DisC_session_t *session, char *jsonData); 9 | 10 | DisC_channel_t *DisC_object_GenerateChannel(DisC_session_t *session, char *jsonData); 11 | 12 | DisC_message_t *DisC_object_GenerateMessage(DisC_session_t *session, char *jsonData); 13 | 14 | DisC_user_t *DisC_object_GenerateUser(DisC_session_t *session, char *jsonData); 15 | 16 | DisC_overwrite_t *DisC_object_GenerateOverwrite(DisC_session_t *session, char *jsonData); 17 | 18 | DisC_role_t *DisC_object_GenerateRole(DisC_session_t *session, char *jsonData); 19 | 20 | DisC_attachment_t *DisC_object_GenerateAttachment(DisC_session_t *session, char *jsonData); 21 | 22 | DisC_embed_t *DisC_object_GenerateEmbed(DisC_session_t *session, char *jsonData); 23 | 24 | DisC_reaction_t *DisC_object_GenerateReaction(DisC_session_t *session, char *jsonData); 25 | 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | 30 | #endif /* OBJECT_H__*/ 31 | -------------------------------------------------------------------------------- /include/DisC/DisC_rest.h: -------------------------------------------------------------------------------- 1 | #ifndef REST_H__ 2 | #define REST_H__ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "DisC_types.h" 9 | 10 | #define DISC_REST_HOSTNAME "discordapp.com:https" //hostname 11 | #define DISC_REST_HOST "discordapp.com" //host 12 | #define DISC_REST_APIBASE "/api/v6" //apiBase 13 | 14 | short DisC_REST_Init(); 15 | 16 | short DisC_REST_ReconnectSession(DisC_session_t *session); 17 | 18 | short DisC_REST_InitSession(DisC_session_t *session); 19 | 20 | short DisC_REST_DestroySession(DisC_session_t *session); 21 | 22 | short DisC_REST_DiscordHTTPRequest(DisC_session_t *session, char **returnBody, unsigned long *returnBodyLen, char **returnCode, char *method, char *mime, char *URI, char *data, unsigned long datalen); 23 | 24 | //============================================================== 25 | 26 | short DisC_REST_GetChannel(DisC_session_t *session, DisC_snowflake_t channelId, DisC_channel_t **channel); 27 | 28 | short DisC_REST_ModifyChannel(DisC_session_t *session, DisC_snowflake_t channelId, char *name, int position, char *topic, DisC_BOOL_t nsfw, int bitrate, int userLimit, DisC_overwrite_t *overwrites, DisC_snowflake_t parentId); 29 | 30 | short DisC_REST_DeleteChannel(DisC_session_t *session, DisC_snowflake_t channelId); 31 | 32 | short DisC_REST_GetChannelMessages(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t around, DisC_snowflake_t before, DisC_snowflake_t after, int limit, DisC_message_t **messages, unsigned long *messageNum); 33 | 34 | short DisC_REST_GetChannelMessage(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId, DisC_message_t **message); 35 | 36 | short DisC_REST_CreateMessage(DisC_session_t *session, DisC_snowflake_t channelId, char *content, DisC_snowflake_t nonce, DisC_BOOL_t tts, unsigned char *fileData, unsigned long fileDataLen, DisC_embed_t *embed); 37 | 38 | short DisC_REST_CreateReaction(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId, DisC_emoji_t *emoji); 39 | 40 | short DisC_REST_DeleteOwnReaction(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId, DisC_emoji_t *emoji); 41 | 42 | short DisC_REST_DeleteUserReaction(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId, DisC_emoji_t *emoji, DisC_snowflake_t userId); 43 | 44 | short DisC_REST_GetReactions(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId, DisC_emoji_t *emoji, DisC_user_t *users); 45 | 46 | short DisC_REST_DeleteAllReactions(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId); 47 | 48 | short DisC_REST_EditMessage(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId); 49 | 50 | short DisC_REST_DeleteMessage(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId); 51 | 52 | short DisC_REST_BulkDeleteMessages(DisC_session_t *session, DisC_snowflake_t channelId); 53 | 54 | short DisC_REST_EditChannelPermissions(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t overwriteId); 55 | 56 | short DisC_REST_GetChannelInvites(DisC_session_t *session, DisC_snowflake_t channelId, DisC_invite_channel_t *invites); 57 | 58 | short DisC_REST_CreateChannelInvite(DisC_session_t *session, DisC_snowflake_t channelId, DisC_invite_channel_t *invite); 59 | 60 | short DisC_REST_DeleteChannelPermission(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t overwriteId); 61 | 62 | short DisC_REST_TriggerTypingIndicator(DisC_session_t *session, DisC_snowflake_t channelId); 63 | 64 | short DisC_REST_GetPinnedMessages(DisC_session_t *session, DisC_snowflake_t channelId, DisC_message_t *messages); 65 | 66 | short DisC_REST_AddPinnedChannelMessage(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId); 67 | 68 | short DisC_REST_DeletePinnedChannelMessage(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId); 69 | 70 | short DisC_REST_GroupDMAddRecipient(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t userId); 71 | 72 | short DisC_REST_GroupDMRemoveRecipient(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t userId); 73 | 74 | //=========== 75 | 76 | #ifdef __cplusplus 77 | } 78 | #endif 79 | 80 | #endif /* REST_H__*/ 81 | -------------------------------------------------------------------------------- /include/DisC/DisC_types.h: -------------------------------------------------------------------------------- 1 | #ifndef TYPES_H__ 2 | #define TYPES_H__ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define DISC_RECONNECT_DELAY 10//number of seconds to wait before reconnecting 13 | #define DISC_MAX_RECONNECTS -1//-1 for infinite, but any other number makes it give up 14 | #define DISC_MAX_TIMEOUT 200 15 | #define DISC_SNOWFLAKE_ALLOCSIZE 19 16 | 17 | //typedef uint64_t DisC_snowflake_t; 18 | typedef char* DisC_snowflake_t; 19 | typedef uint8_t DisC_BOOL_t; 20 | typedef uint32_t DisC_sessionID_t; //no need for anything larger. No way more than uint sessions will be used ever 21 | 22 | 23 | enum DisC_BOOL_enum 24 | { 25 | DISC_FALSE, 26 | DISC_TRUE 27 | }; 28 | 29 | 30 | enum DisC_accountTypes 31 | { 32 | DISC_ACCOUNT_TYPE_USER, 33 | DISC_ACCOUNT_TYPE_BOT 34 | }; 35 | 36 | 37 | typedef struct 38 | { 39 | char *token; 40 | DisC_BOOL_t clientType; 41 | short logLevel; 42 | const char *internalName;//used for identifying what session is which in logs. Just for fun 43 | const char *logFileLocation; 44 | //lib handles the rest of these 45 | DisC_sessionID_t DONOTSET_id; 46 | u_int64_t DONOTSET_lastGWTick; 47 | u_int64_t DONOTSET_lastRESTTick; 48 | u_int64_t DONOTSET_lastHeartbeatTick; 49 | u_int32_t DONOTSET_lastSeqNum;//needed for the sequence num in heartbeats 50 | unsigned short DONOTSET_currentError; 51 | int DONOTSET_heartbeat_interval; 52 | SSL_CTX *DONOTSET_rest_ctx; 53 | BIO *DONOTSET_rest_bio; 54 | SSL_CTX *DONOTSET_gateway_ctx; 55 | BIO *DONOTSET_gateway_bio; 56 | //DisC_callbacks_t *DONOTSET_callbacks; make it store it differently 57 | } DisC_session_t; 58 | 59 | 60 | 61 | 62 | typedef void (*DisC_callback_t)(DisC_session_t *session, void *object, unsigned int type); 63 | //typedef void (*DisC_callback_t)(void *object, unsigned int type); 64 | 65 | 66 | 67 | typedef struct 68 | { 69 | DisC_callback_t ready; 70 | DisC_callback_t resumed; 71 | DisC_callback_t channel_create; 72 | DisC_callback_t channel_update; 73 | DisC_callback_t channel_delete; 74 | DisC_callback_t guild_create; 75 | DisC_callback_t guild_update; 76 | DisC_callback_t guild_delete; 77 | DisC_callback_t guild_ban_add; 78 | DisC_callback_t guild_ban_remove; 79 | DisC_callback_t guild_emoji_update; 80 | DisC_callback_t guild_integrations_update; 81 | DisC_callback_t guild_member_add; 82 | DisC_callback_t guild_member_remove; 83 | DisC_callback_t guild_member_update; 84 | DisC_callback_t guild_members_chunk; 85 | DisC_callback_t guild_role_create; 86 | DisC_callback_t guild_role_update; 87 | DisC_callback_t guild_role_delete; 88 | DisC_callback_t message_create; 89 | DisC_callback_t message_update; 90 | DisC_callback_t message_delete; 91 | DisC_callback_t message_delete_bulk; 92 | DisC_callback_t presence_update; 93 | DisC_callback_t game_object;//this is an event? 94 | DisC_callback_t typing_start; 95 | DisC_callback_t user_update; 96 | DisC_callback_t voice_state_update; 97 | DisC_callback_t voice_server_update; 98 | } DisC_callbacks_t; 99 | 100 | //discord gateway objects 101 | enum DisC_event_data_type 102 | { 103 | DISC_EVENTDATA_TYPE_OBJECT, 104 | DISC_EVENTDATA_TYPE_INTEGER, 105 | DISC_EVENTDATA_TYPE_BOOL 106 | }; 107 | 108 | enum DisC_gateway_opcode 109 | { 110 | DISC_OP_DISPATCH, 111 | DISC_OP_HEARTBEAT, 112 | DISC_OP_IDENTIFY, 113 | DISC_OP_STATUS_UPDATE, 114 | DISC_OP_VOICE_STATUS_UPDATE, 115 | DISC_OP_VOICE_SERVER_PING, 116 | DISC_OP_RESUME, 117 | DISC_OP_RECONNECT, 118 | DISC_OP_REQUEST_GUILD_MEMBERS, 119 | DISC_OP_INVALID_SESSION, 120 | DISC_OP_HELLO, 121 | DISC_OP_HEARTBEAT_ACK 122 | }; 123 | 124 | enum DisC_event_status 125 | { 126 | DISC_STATUS_ONLINE, 127 | DISC_STATUS_DO_NOT_DISTURB, 128 | DISC_STATUS_IDLE, 129 | DISC_STATUS_INVISIBLE, 130 | DISC_STATUS_OFFLINE 131 | }; 132 | 133 | enum DisC_event_activity 134 | { 135 | DISC_ACTIVITY_PLAYING, 136 | DISC_ACTIVITY_STREAMING, 137 | DISC_ACTIVITY_LISTENING, 138 | DISC_ACTIVITY_WATCHING, 139 | DISC_ACTIVITY_NOTHING 140 | }; 141 | 142 | typedef struct 143 | { 144 | int op; 145 | int dataType;//"(object, integer, bool)" 146 | char *d;//parse json later 147 | int s;//"sequence number, used for resuming sessions and heartbeats" OPCODE 0 148 | char *t;//"the event name for this payload" OPCODE 0 149 | } DisC_gateway_payload_t; 150 | 151 | 152 | //discord REST objects------------------------------------------ 153 | 154 | enum DisC_messageType 155 | { 156 | DISC_MESSAGETYPE_DEFAULT, 157 | DISC_MESSAGETYPE_RECIPIENT_ADD, 158 | DISC_MESSAGETYPE_RECIPIENT_REMOVE, 159 | DISC_MESSAGETYPE_CALL, 160 | DISC_MESSAGETYPE_CHANNEL_NAME_CHANGE, 161 | DISC_MESSAGETYPE_CHANNEL_ICON_CHANGE, 162 | DISC_MESSAGETYPE_CHANNEL_PINNED_MESSAGE, 163 | DISC_MESSAGETYPE_GUILD_MEMBER_JOIN 164 | }; 165 | 166 | typedef struct 167 | { 168 | DisC_snowflake_t id; 169 | char* type; 170 | int allow; 171 | int deny; 172 | } DisC_overwrite_t; 173 | 174 | typedef struct 175 | { 176 | DisC_snowflake_t id; 177 | char *username; 178 | char *discriminator; 179 | char *avatar; 180 | DisC_BOOL_t bot; 181 | DisC_BOOL_t mfa_enabled; 182 | DisC_BOOL_t verified; 183 | char *email; 184 | } DisC_user_t; 185 | 186 | typedef struct 187 | { 188 | DisC_snowflake_t id; 189 | char *name; 190 | int color; 191 | DisC_BOOL_t hoist; 192 | int position; 193 | int permissions; 194 | DisC_BOOL_t managed; 195 | DisC_BOOL_t mentionable; 196 | } DisC_role_t; 197 | 198 | typedef struct 199 | { 200 | char* url; 201 | char* proxy_url; 202 | int height; 203 | int width; 204 | } DisC_embed_thumbnail_t; 205 | 206 | typedef struct 207 | { 208 | char* url; 209 | int height; 210 | int width; 211 | } DisC_embed_video_t; 212 | 213 | typedef struct 214 | { 215 | char* url; 216 | char* proxy_url; 217 | int height; 218 | int width; 219 | } DisC_embed_image_t; 220 | 221 | typedef struct 222 | { 223 | char* name; 224 | char* url; 225 | } DisC_embed_provider_t; 226 | 227 | typedef struct 228 | { 229 | char* name; 230 | char* url; 231 | char* icon_url; 232 | char* proxy_icon_url; 233 | } DisC_embed_author_t; 234 | 235 | typedef struct 236 | { 237 | char* text; 238 | char* icon_url; 239 | char* proxy_icon_url; 240 | } DisC_embed_footer_t; 241 | 242 | typedef struct 243 | { 244 | DisC_snowflake_t id; 245 | char* filename; 246 | int size; 247 | char* url; 248 | char* proxy_url; 249 | int height; 250 | int width; 251 | } DisC_attachment_t; 252 | 253 | typedef struct 254 | { 255 | DisC_snowflake_t id; 256 | char* name; 257 | } DisC_emoji_t; 258 | 259 | typedef struct 260 | { 261 | int count; 262 | int me; 263 | DisC_emoji_t emoji; 264 | } DisC_reaction_t; 265 | 266 | typedef struct 267 | { 268 | char* name; 269 | char* value; 270 | int isInline; 271 | } DisC_embed_field_t; 272 | 273 | typedef struct 274 | { 275 | char* title; 276 | char* type; 277 | char* description; 278 | char* url; 279 | char* timestamp;//lookup this format so it can be parsed 280 | int color; 281 | DisC_embed_footer_t footer; 282 | DisC_embed_image_t image; 283 | DisC_embed_thumbnail_t thumbnail; 284 | DisC_embed_video_t video; 285 | DisC_embed_provider_t provider; 286 | DisC_embed_author_t author; 287 | unsigned long fieldCount; 288 | DisC_embed_field_t *fields; //TODO this is an array. needs to be allocated and freed 289 | } DisC_embed_t; 290 | 291 | typedef struct 292 | { 293 | DisC_user_t user; 294 | char *nick; 295 | unsigned long roleCount; 296 | DisC_role_t *roles; 297 | char *joined_at;//parse this later. Its a datetime 298 | DisC_BOOL_t deaf; 299 | DisC_BOOL_t mute; 300 | } DisC_member_t; 301 | 302 | typedef struct 303 | { 304 | DisC_snowflake_t id; 305 | char *name; 306 | } DisC_account_t; 307 | 308 | typedef struct 309 | { 310 | char *name; 311 | int type; 312 | char *url; 313 | } DisC_game_t; 314 | 315 | typedef struct 316 | { 317 | DisC_user_t user; 318 | unsigned long roleCount; 319 | DisC_snowflake_t *roles; 320 | DisC_game_t game; 321 | DisC_snowflake_t guild_id; 322 | char *status; 323 | } DisC_presence_t; 324 | 325 | typedef struct 326 | { 327 | DisC_snowflake_t id; 328 | char *name; 329 | char *type; 330 | DisC_BOOL_t enabled; 331 | DisC_BOOL_t syncing; 332 | DisC_snowflake_t role_id; 333 | int expire_behavior; 334 | int expire_grace_period; 335 | DisC_user_t user; 336 | DisC_account_t account; 337 | char *synced_at; //format this later 338 | } DisC_integration_t; 339 | 340 | typedef struct 341 | { 342 | DisC_snowflake_t guild_id; 343 | DisC_snowflake_t channel_id; 344 | DisC_snowflake_t user_id; 345 | char *sssion_id; 346 | DisC_BOOL_t deaf; 347 | DisC_BOOL_t mute; 348 | DisC_BOOL_t self_deaf; 349 | DisC_BOOL_t self_mute; 350 | DisC_BOOL_t suppress; 351 | } DisC_voice_t; 352 | 353 | typedef struct 354 | { 355 | DisC_snowflake_t id; 356 | DisC_snowflake_t guild_id; 357 | char* name; 358 | int type;//going to be text or voice 359 | int position; 360 | DisC_BOOL_t isNsfw; 361 | unsigned long permOverwritesCount; 362 | DisC_overwrite_t *permission_overwrites; 363 | char* topic; 364 | unsigned long recipientCount; 365 | DisC_user_t *recipients; 366 | char *icon; 367 | DisC_snowflake_t owner_id; 368 | DisC_snowflake_t application_id; 369 | DisC_snowflake_t parent_id; 370 | DisC_snowflake_t last_message_id; 371 | char *last_pin_timestamp; 372 | int bitrate; 373 | int user_limit; 374 | } DisC_channel_t; 375 | 376 | typedef struct 377 | { 378 | DisC_user_t inviter; 379 | int uses; 380 | int max_uses; 381 | int max_age; 382 | DisC_BOOL_t temporary; 383 | char *created_at; //convert this 384 | DisC_BOOL_t revoked; 385 | } DisC_invite_metadata_t; 386 | 387 | typedef struct 388 | { 389 | DisC_snowflake_t id; 390 | char *name; 391 | char *splash; 392 | char *icon; 393 | } DisC_invite_guild_t; 394 | 395 | typedef struct//thanks boysanic 396 | { 397 | DisC_snowflake_t channel_id; 398 | char *name; 399 | char *type; 400 | } DisC_invite_channel_t; 401 | 402 | typedef struct 403 | { 404 | DisC_snowflake_t id; 405 | DisC_BOOL_t is_private; 406 | DisC_user_t recipient; 407 | DisC_snowflake_t last_message_id; 408 | } DisC_dm_channel_t; 409 | 410 | typedef struct 411 | { 412 | DisC_snowflake_t id; 413 | DisC_snowflake_t channel_id; 414 | DisC_user_t author; 415 | char *content; 416 | char *timestamp; 417 | char *edited_timestamp; 418 | int tts; 419 | int mention_everyone; 420 | unsigned long mentionCount; 421 | DisC_user_t *mentions; //TODO you have to malloc this stuff. Its an array of users 422 | unsigned long roleCount; 423 | DisC_snowflake_t *roles; //TODO same as before 424 | unsigned long attachmentCount; 425 | DisC_attachment_t *attatchments; //TODO same as before yet again 426 | unsigned long embedCount; 427 | DisC_embed_t *embeds; //TODO same 428 | unsigned long reactionCount; 429 | DisC_reaction_t *reactions; //same again 430 | DisC_snowflake_t nonce; 431 | int pinned; 432 | char* webhook_id; 433 | int type; 434 | } DisC_message_t; 435 | 436 | typedef struct 437 | { 438 | DisC_snowflake_t id; 439 | char *name; 440 | char *icon; 441 | char *splash; 442 | DisC_snowflake_t owner_id; 443 | char *region; 444 | DisC_snowflake_t afk_channel_id; 445 | int afk_timeout; 446 | DisC_BOOL_t embed_enabled; 447 | DisC_snowflake_t embed_channel_id; 448 | int verification_level; 449 | int default_message_notifications; 450 | unsigned long roleCount; 451 | DisC_role_t *roles; 452 | unsigned long emojiCount; 453 | DisC_emoji_t *emojis; 454 | unsigned long featureCount; 455 | //Features *features; //???? 456 | int mfa_level; 457 | char *joined_at;//this is a datetime. Format this 458 | DisC_BOOL_t large; 459 | DisC_BOOL_t unavailable; 460 | int member_count; 461 | unsigned long voiceStateCount; 462 | DisC_voice_t *voice_states;//array of voice states 463 | unsigned long memberCount; 464 | DisC_member_t *members;//array 465 | unsigned long channelCount; 466 | DisC_channel_t *channels; 467 | unsigned long precenceCount; 468 | DisC_presence_t *presences; 469 | } DisC_guild_t; 470 | 471 | typedef struct 472 | { 473 | char *code; 474 | DisC_guild_t guild; 475 | DisC_channel_t channel; 476 | } DisC_invite_t; 477 | 478 | //discord gateway events---------------------------------------- 479 | 480 | typedef struct 481 | { 482 | DisC_BOOL_t isSince; 483 | int since;//only read if using since 484 | DisC_game_t game; 485 | short status; 486 | DisC_BOOL_t afk; 487 | } DisC_gateway_status_update_t; 488 | 489 | #ifdef __cplusplus 490 | } 491 | #endif 492 | 493 | #endif /* TYPES_H__*/ 494 | -------------------------------------------------------------------------------- /include/DisC/DisC_util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H__ 2 | #define UTIL_H__ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #define DISC_VER_MAJOR 0 9 | #define DISC_VER_MINOR 0 10 | #define DISC_VER_REVISION 1 11 | 12 | enum DisC_logLevel 13 | { 14 | DISC_LOG_ERROR_ONLY, 15 | DISC_LOG_VERBOSE, 16 | DISC_LOG_VERBOSE_AND_PRINT 17 | }; 18 | 19 | enum DisC_logSeverity 20 | { 21 | DISC_SEVERITY_NOTIFY, 22 | DISC_SEVERITY_WARNING, 23 | DISC_SEVERITY_ERROR 24 | }; 25 | 26 | short DisC_util_ReadFile(const char *path, unsigned char **data, unsigned long *size); 27 | 28 | short DisC_util_WriteFile(const char *path, unsigned char *data, unsigned long *size); 29 | 30 | void DisC_Log(short logLevel, const char *logLoc, unsigned short severity, const char *fmt, ...); 31 | 32 | char *DisC_strmkdup(const char *str); 33 | 34 | short DisC_asprintf(char **string, const char *fmt, ...); 35 | 36 | void DisC_Delay(unsigned long milisec); 37 | 38 | u_int64_t DisC_GetTick(); 39 | 40 | //stupid thing copied from stackoverflow. Im only using it to check my websocket stuff. Will delete 41 | void print_bits(unsigned char octet);//TODO remove this 42 | 43 | //=========================================================================================================== 44 | 45 | short Disc_util_FreeChannel(DisC_session_t *session, DisC_channel_t *channel); 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | #endif /* UTIL_H__*/ 52 | -------------------------------------------------------------------------------- /src/DisC.c: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include 3 | #endif 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "DisC.h" 10 | 11 | /*The only global variables in DisC*/ 12 | DisC_session_t **sessions = NULL; 13 | 14 | unsigned long sessionCount = 0; 15 | /*===================================================*/ 16 | 17 | short DisC_InitLibrary() 18 | { 19 | DisC_REST_Init(); 20 | 21 | return DISC_ERROR_NONE; 22 | } 23 | 24 | short DisC_DestroyLibrary() 25 | { 26 | 27 | 28 | 29 | return DISC_ERROR_NONE; 30 | } 31 | 32 | short DisC_CreateSession(DisC_session_t *session, DisC_callbacks_t *callbacks) 33 | { 34 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Starting session %s", session->internalName); 35 | 36 | DisC_REST_InitSession(session);//TODO do error checking 37 | 38 | DisC_gateway_InitSession(session, callbacks);//TODO do error checking 39 | 40 | 41 | //TODO add session to array of sessions 42 | //TODO add callbacks to array of callbacks 43 | 44 | if(sessionCount == 0) 45 | { 46 | sessions = malloc(sizeof(DisC_session_t *)); 47 | if(sessions == NULL) 48 | { 49 | exit(1); 50 | } 51 | } 52 | else 53 | { 54 | sessions = realloc(sessions, sizeof(DisC_session_t *)); 55 | if(sessions == NULL)//TODO replace every one of these exits with error messages 56 | { 57 | exit(1); 58 | } 59 | } 60 | 61 | sessions[sessionCount] = session; 62 | sessionCount++; 63 | 64 | 65 | DisC_AddError(session, DISC_ERROR_NONE); 66 | return DISC_ERROR_NONE; 67 | } 68 | 69 | short DisC_DesroySession(DisC_session_t *session) 70 | { 71 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Ending session %s", session->internalName); 72 | 73 | DisC_gateway_DestroySession(session);//destroy in the opposite order from createSession 74 | 75 | DisC_REST_DestroySession(session);//TODO generally check errors on everything 76 | 77 | //TODO actually remove pointer from array of session pointers 78 | sessionCount--; 79 | 80 | DisC_AddError(session, DISC_ERROR_NONE); 81 | return DISC_ERROR_NONE; 82 | } 83 | 84 | short DisC_StartAllSessions() 85 | { 86 | //DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Entering loop"); 87 | printf("Enterring loop\n"); 88 | //TODO make a custom loop that runs in threads 89 | unsigned long i; 90 | while(sessionCount > 0) 91 | { 92 | //loop through all discord gateway sockets and check for data. TODO use select or some polling thing. 93 | 94 | for(i = 0; i < sessionCount; i++) 95 | { 96 | DisC_gateway_ListenAndManage(sessions[i]); 97 | } 98 | 99 | DisC_Delay(500);//delay half a second 100 | 101 | //call a post loop function 102 | } 103 | 104 | return DISC_ERROR_NONE; 105 | } 106 | -------------------------------------------------------------------------------- /src/DisC_errors.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "DisC_errors.h" 5 | 6 | const char *errors[] = {//never set so thread safe 7 | "Operation was successfull.\0", 8 | "There was a generic error. This is most likely not severe.\0", 9 | "The account has insufficient permissions to do this.\0", 10 | "The API server returned an error that isn't defined.\0", 11 | "The session has lost connection or not connected.\0" 12 | "The object requested doesnt exist.\0" 13 | "Either the message was too large or what was sent exceeded limits for Discord.\0" 14 | "Not enough ram or memory corruption.\0" 15 | }; 16 | 17 | 18 | void DisC_PrintError(DisC_session_t *session) 19 | { 20 | printf("%s\n", errors[session->DONOTSET_currentError]); 21 | } 22 | 23 | const char *DisC_GetSessionErrorString(DisC_session_t *session) 24 | { 25 | return errors[session->DONOTSET_currentError]; 26 | } 27 | 28 | const char *DisC_GetErrorString(unsigned short error) 29 | { 30 | return errors[error]; 31 | } 32 | 33 | void DisC_AddError(DisC_session_t *session, int error) 34 | { 35 | session->DONOTSET_currentError = error; 36 | } 37 | -------------------------------------------------------------------------------- /src/DisC_gateway.c: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include 3 | #else 4 | #include 5 | #endif 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | //#include 13 | 14 | #include 15 | 16 | #include "DisC_types.h" 17 | #include "DisC_util.h" 18 | #include "DisC_errors.h" 19 | #include "DisC_object.h" 20 | #include "DisC_rest.h" 21 | #include "DisC_gateway.h" 22 | 23 | 24 | 25 | 26 | static short internal_CheckSendHeartbeat(DisC_session_t *session); 27 | 28 | static char *internal_GetHTTPResponseCode(DisC_session_t *session, char *str); 29 | 30 | static char *internal_GenerateUpgradeHeaders(DisC_session_t *session, char *b64Key, char *URI, char *host); 31 | 32 | static unsigned char *internal_ReadData(DisC_session_t *session, unsigned long *dataLen, DisC_BOOL_t isHandshaking); 33 | 34 | static short internal_WriteData(DisC_session_t *session, unsigned char *data, unsigned long dataLen, DisC_BOOL_t isWebsocket); 35 | 36 | //======================================================================================================= 37 | 38 | 39 | static short internal_CheckSendHeartbeat(DisC_session_t *session)//TODO completely redo the networking system so long blocking operations don't interrupt heartbeats 40 | { 41 | //TODO run this function from the check and manage function 42 | 43 | uint64_t tick = DisC_GetTick(); 44 | 45 | //check last tick heartbeat was sent at 46 | //if tick is after the tick wait, send a heartbeat, then check for opcode 11 47 | //otherwise, don' do anything 48 | 49 | //TODO make after doing the gateway identify 50 | 51 | if(((session->DONOTSET_lastHeartbeatTick) + session->DONOTSET_heartbeat_interval) <= tick)//check if the current tick is the one in the past plus the heartbeat interval 52 | { 53 | unsigned char *eventData; 54 | 55 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Heartbeating"); 56 | session->DONOTSET_lastHeartbeatTick = tick; 57 | 58 | if(session->DONOTSET_lastSeqNum == UINT32_MAX) 59 | { 60 | eventData = "null";//set it to NULL 61 | } 62 | else 63 | { 64 | eventData = json_dumps(json_integer(session->DONOTSET_lastSeqNum), 0); 65 | } 66 | DisC_gateway_SendEvent(session, DISC_OP_HEARTBEAT, eventData, strlen(eventData)); 67 | } 68 | 69 | //printf("clock: %lu clock + future: %lu\n", tick, ((session->DONOTSET_lastHeartbeatTick) + session->DONOTSET_heartbeat_interval)); 70 | 71 | 72 | return DISC_ERROR_NONE; 73 | } 74 | 75 | static char *internal_GetHTTPResponseCode(DisC_session_t *session, char *str) 76 | { 77 | //make a temp string to verify the send 78 | //temporary fix for chunked response still sending data that code later mistakes for the end of headers 79 | if(*str == '0') 80 | { 81 | if(strstr(str, "\r\n\r\n") == &str[1]) 82 | { 83 | str[1] = "eror"; 84 | } 85 | } 86 | char *tempReturn = NULL; 87 | DisC_asprintf(&tempReturn, "%s", strstr(str, "HTTP/1.1 ")); 88 | char *httpStatus = strtok(tempReturn, "\r\n"); 89 | char *code = httpStatus + strlen("HTTP/1.1 "); 90 | strtok(code, " "); 91 | //printf("str: %s\ncode: %s\nand: %s\ncode: %s\n", str, httpStatus, tempReturn, code); 92 | //finish getting status 93 | 94 | char *returnStr = DisC_strmkdup(code); 95 | free(tempReturn); 96 | 97 | return returnStr; 98 | } 99 | 100 | static char *internal_GenerateUpgradeHeaders(DisC_session_t *session, char *b64Key, char *URI, char *host) 101 | { 102 | char *finalString = NULL; 103 | char *base = "GET %s?v=6&encoding=json HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\nAuthorization: %s%s\r\nUser-Agent: (so sorry for not obeying rate limiting. Nearly at the point that I can add it)DisC v%d.%d.%d: %s\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-webSocket-Key: %s\r\nSec-WebSocket-Protocol: chat, superchat\r\nSec-WebSocket-Version: 13\r\n\r\n"; 104 | DisC_asprintf(&finalString, base, URI, host, session->clientType ? "Bot " : "", session->token, DISC_VER_MAJOR, DISC_VER_MINOR, DISC_VER_REVISION, session->internalName, b64Key); 105 | 106 | //char *base = "GET %s HTTP/1.1\r\nHost: %s\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-webSocket-Key: %s\r\nSec-WebSocket-Protocol: chat, superchat\r\nSec-WebSocket-Version: 13\r\n\r\n"; 107 | //DisC_asprintf(&finalString, base, URI, host, b64Key); 108 | 109 | 110 | return finalString; 111 | } 112 | 113 | 114 | static unsigned char *internal_ReadData(DisC_session_t *session, unsigned long *dataLen, DisC_BOOL_t isHandshaking) 115 | { 116 | char *buffer = NULL; 117 | unsigned long byteCount = 0; 118 | unsigned long byteChunkMax = 1024; 119 | unsigned long contentLength = 0; 120 | unsigned long status; 121 | int foundHTTPEnd = 0; 122 | 123 | buffer = malloc(byteChunkMax); 124 | if(buffer == NULL) 125 | { 126 | exit(1); 127 | } 128 | status = byteChunkMax; 129 | do 130 | { 131 | status = (unsigned long)BIO_read(session->DONOTSET_gateway_bio, buffer + byteCount, status); 132 | if(status == -1) 133 | { 134 | return NULL; 135 | } 136 | 137 | byteCount += status; 138 | 139 | buffer = realloc(buffer, byteCount + byteChunkMax); 140 | //printf("%d\n", status); 141 | 142 | 143 | //printf("buffer: %s\nstatus: %lu", buffer, status); 144 | if(isHandshaking) 145 | { 146 | if(strstr(buffer, "\r\n\r\n") != NULL && isHandshaking) 147 | { 148 | foundHTTPEnd = 1; 149 | } 150 | else if(strchr(buffer, 0x00)) 151 | { 152 | foundHTTPEnd = 1;//reusing variable 153 | } 154 | } 155 | 156 | 157 | 158 | 159 | } 160 | while(status > 0 && !foundHTTPEnd); 161 | 162 | 163 | 164 | 165 | buffer = realloc(buffer, byteCount); 166 | 167 | *dataLen = byteCount; 168 | return buffer; 169 | } 170 | 171 | static short internal_WriteData(DisC_session_t *session, unsigned char *data, unsigned long dataLen, DisC_BOOL_t isWebsocket) 172 | { 173 | //refer to https://tools.ietf.org/html/rfc6455#page-27 174 | //setup websocket frame. No doubt that im doing this totally wrong 175 | unsigned char *maskedData = NULL, *finalData = NULL; 176 | //TODO actually make this automatically 177 | u_int8_t controlAndOpcode = 0b10000001; 178 | //u_int8_t maskAndLength = 0b10000000; 179 | u_int8_t maskAndLength = 0x80; 180 | //u_int8_t partialMask = (uint8_t)rand(); 181 | //u_int32_t mask = ((u_int32_t)rand()) * 2; 182 | u_int32_t mask = UINT32_MAX;//NOTE temporary 183 | u_int64_t finalDataLen, i; 184 | unsigned int numberOffset = 0; 185 | 186 | 187 | if(isWebsocket) 188 | { 189 | maskedData = malloc(sizeof(unsigned char) * dataLen); 190 | if(maskedData == NULL) 191 | { 192 | DisC_AddError(session, DISC_ERROR_MEMORY); 193 | return DISC_ERROR_MEMORY; 194 | } 195 | //memcpy(maskedData, data, dataLen); 196 | 197 | //mask data 198 | //maskedData = maskedData^mask; 199 | 200 | for(i = 0; i <= dataLen; i++) 201 | { 202 | //maskedData[i] = data[i]^*(uint8_t *)(&mask + (i % 4)); 203 | //printf("mask: %c %c %u\n", maskedData[i], maskedData[i]^*(uint8_t *)(&mask + (i % 4)), (i % 4)); 204 | 205 | //maskedData[i] = data[i]^(mask >> (8 * (i % 4))); 206 | //printf("mask: %c %c %u\n", maskedData[i], maskedData[i]^(mask << 8 * (i % 4)), (i % 4)); 207 | 208 | maskedData[i] = data[i]^(uint8_t)0xFF; 209 | //printf("mask: %c %c %u\n", maskedData[i], maskedData[i]^(uint8_t)0xFF, (i % 4)); 210 | } 211 | 212 | 213 | 214 | 215 | //encode length 216 | 217 | 218 | 219 | if(dataLen <= 125) 220 | { 221 | maskAndLength = maskAndLength^(uint8_t)dataLen;//copy the second byte 222 | finalData = calloc(1, sizeof(uint16_t) + sizeof(u_int32_t) + dataLen); 223 | finalDataLen = sizeof(uint16_t) + sizeof(u_int32_t) + dataLen; 224 | } 225 | else if(dataLen <= UINT16_MAX)//make it fix a 16 bit number 226 | { 227 | maskAndLength = maskAndLength^(uint8_t)126; 228 | finalData = calloc(1, sizeof(uint16_t) + sizeof(uint16_t) + sizeof(u_int32_t) + dataLen); 229 | numberOffset = sizeof(uint16_t); 230 | finalDataLen = sizeof(uint16_t) + sizeof(uint16_t) + sizeof(u_int32_t) + dataLen; 231 | //uint16_t *tempNum = (uint16_t *)&finalData[sizeof(uint16_t) + numberOffset];//remove number offset? yep. Oh well, doing bitshifting I guess 232 | //*tempNum = (uint16_t)dataLen; 233 | finalData[2] = dataLen >> 8; 234 | finalData[3] = dataLen; 235 | } 236 | else//its only able to fit in a 64 bit number 237 | { 238 | maskAndLength = maskAndLength^(uint8_t)127; 239 | finalData = calloc(1, sizeof(uint16_t) + sizeof(uint64_t) + sizeof(u_int32_t) + dataLen); 240 | numberOffset = sizeof(uint64_t); 241 | finalDataLen = sizeof(uint16_t) + sizeof(uint64_t) + sizeof(u_int32_t) + dataLen; 242 | //uint64_t *tempNum = (uint64_t *)&finalData[sizeof(uint16_t) + numberOffset];//remove number offset? 243 | //*tempNum = (uint64_t)dataLen; 244 | for(i = 0; i < 8; i++) 245 | { 246 | finalData[2 + i] = dataLen >> (8 * i); 247 | } 248 | } 249 | //NOTE to future me. Change the websocket testing server back to the discord one in all the areas affected. 250 | 251 | finalData[0] = controlAndOpcode; 252 | finalData[1] = maskAndLength; 253 | //uint32_t *tempMaskLoc = (uint32_t *)&finalData[sizeof(uint16_t) + numberOffset + sizeof(uint32_t)];//uint16_t so I dont have to multiply twice 254 | //*tempMaskLoc = mask; 255 | finalData[sizeof(uint16_t) + numberOffset] = (mask >> 24); 256 | finalData[sizeof(uint16_t) + numberOffset + 1] = (mask >> 16); 257 | finalData[sizeof(uint16_t) + numberOffset + 2] = (mask >> 8); 258 | finalData[sizeof(uint16_t) + numberOffset + 3] = mask; 259 | 260 | memcpy(&finalData[sizeof(uint16_t) + numberOffset + sizeof(uint32_t)], maskedData, dataLen); 261 | 262 | //finalData[numberOffset + sizeof(uint32_t) + dataLen] = 0x00; 263 | 264 | //for(i = 0; i < finalDataLen; i++) 265 | //{ 266 | //print_bits(finalData[i]); 267 | //} 268 | //DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Sending websocket with frame: control and opcode: %x|%x, mask and length: %x|%x, length: %x|%x, mask: %x|%x, data %c\n", controlAndOpcode, finalData[0], maskAndLength, finalData[1], (unsigned)dataLen, (uint16_t)(finalData[sizeof(uint16_t) + numberOffset]), mask, (uint32_t)(finalData[sizeof(uint16_t) + numberOffset + sizeof(uint32_t)]), finalData[sizeof(uint16_t) + numberOffset + sizeof(uint32_t) + dataLen]); 269 | 270 | /* 271 | finalData = malloc((sizeof(uint8_t) * 2) + sizeof(uint32_t));//will reallocate this a final time to include the data 272 | if(finalData == NULL) 273 | { 274 | DisC_AddError(session, DISC_ERROR_MEMORY); 275 | return DISC_ERROR_MEMORY; 276 | } 277 | 278 | 279 | memcpy(finalData, &controlAndOpcode, sizeof(uint8_t));//TODO fix ordering 280 | printf("tesssst: %c\n", finalData); 281 | 282 | if(dataLen <= 125) 283 | { 284 | maskAndLength = maskAndLength^(uint8_t)dataLen;//copy the second byte 285 | memcpy(finalData + sizeof(uint8_t), &maskAndLength, sizeof(uint8_t));//write the length 286 | memcpy(finalData + (sizeof(uint8_t) * 2), &mask, sizeof(uint32_t));//copy the mask to the end 287 | 288 | finalData = realloc(finalData, (sizeof(uint8_t) * 2) + sizeof(uint32_t) + (dataLen * sizeof(unsigned char)));//reallocate it to fit the data 289 | if(finalData == NULL) 290 | { 291 | DisC_AddError(session, DISC_ERROR_MEMORY); 292 | return DISC_ERROR_MEMORY; 293 | } 294 | memcpy(finalData + (sizeof(uint8_t) * 2) + sizeof(uint32_t), maskedData, dataLen);//copy the final data after the frame header 295 | finalDataLen = (sizeof(uint8_t) * 2) + sizeof(uint32_t) + (dataLen * sizeof(unsigned char)); 296 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Sending websocket with frame(#0): control and opcode: %x, mask and length: %x, mask: %x, data :%s", controlAndOpcode, maskAndLength, mask, finalData + (sizeof(uint8_t) * 2) + sizeof(uint32_t)); 297 | 298 | } 299 | else if(dataLen > 125 && dataLen < ULONG_MAX)//make it fix a 16 bit number 300 | { 301 | maskAndLength = maskAndLength^(uint8_t)126; 302 | finalData = realloc(finalData, sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t)); 303 | if(finalData == NULL) 304 | { 305 | DisC_AddError(session, DISC_ERROR_MEMORY); 306 | return DISC_ERROR_MEMORY; 307 | } 308 | memcpy(finalData + sizeof(uint8_t), &maskAndLength, sizeof(uint8_t));//copy the second byte 309 | //finalData + (sizeof(uint8_t) + sizeof(uint16_t)) = (uint16_t)dataLen;//write the length 310 | uint16_t tempLen = (uint16_t)dataLen; 311 | memcpy(finalData + (sizeof(uint8_t) + sizeof(uint16_t)), &tempLen, sizeof(uint16_t));//write the length 312 | memcpy(finalData + (sizeof(uint8_t) + sizeof(uint16_t)), &mask, sizeof(uint32_t));//copy the mask to the end 313 | 314 | finalData = realloc(finalData, (sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t)) + (dataLen * sizeof(unsigned char)));//reallocate it to fit the data 315 | if(finalData == NULL) 316 | { 317 | DisC_AddError(session, DISC_ERROR_MEMORY); 318 | return DISC_ERROR_MEMORY; 319 | } 320 | memcpy(finalData + (sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t)), maskedData, dataLen);//copy the final data after the frame header 321 | finalDataLen = (sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t)) + (dataLen * sizeof(unsigned char)); 322 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Sending websocket with frame(#1): control and opcode: %c|%c, mask and length: %c|%c, mask: %x|%x, data :%s", controlAndOpcode, finalData, maskAndLength, finalData + sizeof(uint8_t), mask, finalData + (sizeof(uint8_t) + sizeof(uint16_t)), finalData + (sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t))); 323 | 324 | } 325 | else//its only able to fit in a 64 bit number 326 | { 327 | maskAndLength = maskAndLength^(uint8_t)127; 328 | finalData = realloc(finalData, sizeof(uint8_t) + sizeof(uint64_t) + sizeof(uint32_t)); 329 | if(finalData == NULL) 330 | { 331 | DisC_AddError(session, DISC_ERROR_MEMORY); 332 | return DISC_ERROR_MEMORY; 333 | } 334 | memcpy(finalData + sizeof(uint8_t), &maskAndLength, sizeof(uint8_t));//copy the second byte 335 | //finalData + (sizeof(uint8_t) + sizeof(uint64_t)) = (uint64_t)dataLen;//write the length 336 | uint64_t tempLen = (uint64_t)dataLen; 337 | memcpy(finalData + (sizeof(uint8_t) + sizeof(uint64_t)), &tempLen, sizeof(uint64_t));//write the length 338 | memcpy(finalData + (sizeof(uint8_t) + sizeof(uint64_t)), &mask, sizeof(uint32_t));//copy the mask to the end 339 | 340 | finalData = realloc(finalData, (sizeof(uint8_t) + sizeof(uint64_t) + sizeof(uint32_t)) + (dataLen * sizeof(unsigned char)));//reallocate it to fit the data 341 | if(finalData == NULL) 342 | { 343 | DisC_AddError(session, DISC_ERROR_MEMORY); 344 | return DISC_ERROR_MEMORY; 345 | } 346 | memcpy(finalData + (sizeof(uint8_t) + sizeof(uint64_t) + sizeof(uint32_t)), maskedData, dataLen);//copy the final data after the frame header 347 | finalDataLen = (sizeof(uint8_t) + sizeof(uint64_t) + sizeof(uint32_t)) + (dataLen * sizeof(unsigned char)); 348 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Sending websocket with frame(#2): control and opcode: %x, mask and length: %x, mask: %x, data :%s", controlAndOpcode, maskAndLength, mask, finalData + (sizeof(uint8_t) + sizeof(uint64_t) + sizeof(uint32_t))); 349 | 350 | } 351 | */ 352 | 353 | if(BIO_write(session->DONOTSET_gateway_bio, finalData, finalDataLen) <= 0)//TODO TODO TODO TODO check if its a websocket or its the initial upgrade http req 354 | { 355 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Couldnt send to gateway... At the moment, this means further library dysfunction because I dont want to risk being banned from the api if I pull something stupid on accident"); 356 | DisC_AddError(session, DISC_ERROR_CONNECTION); 357 | return DISC_ERROR_CONNECTION; 358 | } 359 | 360 | free(finalData); 361 | free(maskedData); 362 | 363 | } 364 | else 365 | { 366 | if(BIO_write(session->DONOTSET_gateway_bio, data, dataLen) <= 0)//TODO TODO TODO TODO check if its a websocket or its the initial upgrade http req 367 | { 368 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Couldnt send to gateway... At the moment, this means further library dysfunction because I dont want to risk being banned from the api if I pull something stupid on accident"); 369 | DisC_AddError(session, DISC_ERROR_CONNECTION); 370 | return DISC_ERROR_CONNECTION; 371 | } 372 | } 373 | 374 | DisC_AddError(session, DISC_ERROR_NONE); 375 | return DISC_ERROR_NONE; 376 | } 377 | 378 | 379 | short DisC_gateway_InitSession(DisC_session_t *session, DisC_callbacks_t *callbacks) 380 | { 381 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Starting session %s gateway", session->internalName); 382 | short returnval; 383 | char *url = NULL; 384 | char *code = NULL; 385 | char *readData = NULL; 386 | unsigned long readDataLen = 0; 387 | 388 | DisC_asprintf(&url, session->clientType ? "%s/gateway/bot" : "%s/gateway", DISC_REST_APIBASE); 389 | DisC_REST_DiscordHTTPRequest(session, &readData, &readDataLen, &code, "GET", NULL, url, NULL, 0); 390 | 391 | 392 | char *replace = strstr(readData, "}"); 393 | replace++; 394 | *replace = '\0'; 395 | 396 | json_t *retRoot = NULL; 397 | 398 | //============================================ 399 | 400 | if(strcmp(code, "200") == 0) 401 | { 402 | retRoot = json_loads(readData, 0, NULL); 403 | 404 | if(retRoot != NULL) 405 | { 406 | //make a nonblocking socket with ssl stuff 407 | unsigned long retries = 0; 408 | int result; 409 | char *wsurl = json_string_value(json_object_get(retRoot, "url")); 410 | wsurl += strlen("wss://"); 411 | char *finalWSURL = NULL; 412 | DisC_asprintf(&finalWSURL, "%s:https", wsurl); 413 | 414 | //printf() 415 | //=========================================================== 416 | 417 | 418 | session->DONOTSET_gateway_ctx = SSL_CTX_new(TLSv1_2_client_method()); 419 | 420 | 421 | session->DONOTSET_gateway_bio = BIO_new_ssl_connect(session->DONOTSET_gateway_ctx); 422 | BIO_set_conn_hostname(session->DONOTSET_gateway_bio, finalWSURL); 423 | BIO_set_nbio(session->DONOTSET_gateway_bio, 1); 424 | result = BIO_do_connect(session->DONOTSET_gateway_bio); 425 | if(result <= 0) 426 | { 427 | while(BIO_should_retry(session->DONOTSET_gateway_bio) && retries != DISC_MAX_RECONNECTS) 428 | { 429 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_ERROR, "Waiting for socket"); 430 | result = BIO_do_connect(session->DONOTSET_gateway_bio); 431 | if(result > 0) 432 | break; 433 | 434 | 435 | DisC_Delay(1000);//set to one second because this is the only one that actually works for some reason. BIO non blocking is really weird. 436 | retries++;//TODO wait for the socket the right way instead of sleeping. This is going to produce wildly different results for everything 437 | } 438 | } 439 | else 440 | { 441 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Connected to gateway first try"); 442 | DisC_AddError(session, DISC_ERROR_NONE); 443 | returnval = DISC_ERROR_NONE; 444 | } 445 | 446 | if(result <= 0) 447 | { 448 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Could not connect to gateway after retrying max times or there was a serious error"); 449 | DisC_AddError(session, DISC_ERROR_CONNECTION); 450 | returnval = DISC_ERROR_CONNECTION; 451 | } 452 | else 453 | { 454 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Handshaking with gateway"); 455 | //do everything to make a websocket tcp Connection 456 | unsigned char *returnData = NULL; 457 | unsigned long returnDataLen = 0; 458 | char *jsonResponse = NULL; 459 | char *request = internal_GenerateUpgradeHeaders(session, "x3JJHMbDL1EzLkh9GBhXDw==", "/", wsurl);//TODO not use the wikipedia base64 haha 460 | internal_WriteData(session, request, strlen(request), DISC_FALSE); 461 | returnData = internal_ReadData(session, &returnDataLen, DISC_TRUE); 462 | while(returnData == NULL && (errno == EAGAIN || errno == EWOULDBLOCK)) 463 | { 464 | returnData = internal_ReadData(session, &returnDataLen, DISC_TRUE); 465 | DisC_Delay(1000); 466 | } 467 | 468 | //printf("http data: %s\n", returnData); 469 | 470 | code = internal_GetHTTPResponseCode(session, returnData); 471 | free(returnData); 472 | 473 | if(strcmp(code, "101") == 0) 474 | { 475 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Gateway handshake success"); 476 | DisC_AddError(session, DISC_ERROR_NONE); 477 | returnval = DISC_ERROR_NONE; 478 | DisC_gateway_payload_t *internalPayload = NULL; 479 | 480 | //parse json now 481 | returnData = internal_ReadData(session, &returnDataLen, DISC_FALSE); 482 | while(returnData == NULL && (errno == EAGAIN || errno == EWOULDBLOCK)) 483 | { 484 | returnData = internal_ReadData(session, &returnDataLen, DISC_FALSE); 485 | DisC_Delay(1000); 486 | } 487 | jsonResponse = strstr(returnData, "{"); 488 | char *replace = strrchr(jsonResponse, '}'); 489 | replace++; 490 | *replace = '\0'; 491 | //printf("json data: %s\n", jsonResponse); 492 | 493 | internalPayload = DisC_object_GenerateGatewayPayload(session, jsonResponse); 494 | 495 | printf("opcode: %d data: %s\n", internalPayload->op, internalPayload->d); 496 | if(internalPayload->op == DISC_OP_HELLO) 497 | { 498 | json_t *hello = json_loads(internalPayload->d, 0, NULL); 499 | session->DONOTSET_heartbeat_interval = json_integer_value(json_object_get(hello, "heartbeat_interval")) / 1000;//doing this because the gateway payload only gets the common stuff. 500 | printf("Heartbeat interval: %d\n", session->DONOTSET_heartbeat_interval);//Also, the above heartbeat setting is divided by 1000 temporarily 501 | json_decref(hello); 502 | //session->DONOTSET_heartbeat_interval = internalPayload-> 503 | 504 | session->DONOTSET_lastHeartbeatTick = DisC_GetTick(); 505 | session->DONOTSET_lastSeqNum = UINT32_MAX;//unlikely that the sequence will ever hit this number. Still will find a better way to handle the first heartbeat though. 506 | 507 | } 508 | else 509 | { 510 | //error 511 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_ERROR, "Gateway did not reply HELLO"); 512 | DisC_AddError(session, DISC_ERROR_CONNECTION); 513 | returnval = DISC_ERROR_CONNECTION; 514 | } 515 | 516 | 517 | } 518 | else 519 | { 520 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Could not handshake with gateway"); 521 | DisC_AddError(session, DISC_ERROR_CONNECTION); 522 | returnval = DISC_ERROR_CONNECTION; 523 | } 524 | 525 | 526 | free(request); 527 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Connected to gateway"); 528 | DisC_AddError(session, DISC_ERROR_NONE); 529 | returnval = DISC_ERROR_NONE; 530 | } 531 | 532 | free(finalWSURL); 533 | } 534 | else 535 | { 536 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_ERROR, "Failed to parse JSON"); 537 | DisC_AddError(session, DISC_ERROR_MEMORY); 538 | returnval = DISC_ERROR_MEMORY; 539 | } 540 | 541 | } 542 | else if(strcmp(code, "401") == 0) 543 | { 544 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "Insufficient permissions for action"); 545 | DisC_AddError(session, DISC_ERROR_PERMISSIONS); 546 | returnval = DISC_ERROR_PERMISSIONS; 547 | } 548 | else{ 549 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "HTTP error: %s: %s", code, readData); 550 | DisC_AddError(session, DISC_ERROR_GENERIC); 551 | returnval = DISC_ERROR_GENERIC; 552 | } 553 | 554 | free(code); 555 | free(readData); 556 | free(url); 557 | 558 | return returnval; 559 | } 560 | 561 | short DisC_gateway_DestroySession(DisC_session_t *session) 562 | { 563 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Disconnecting %s from gateway...", session->internalName); 564 | BIO_free_all(session->DONOTSET_gateway_bio); 565 | SSL_CTX_free(session->DONOTSET_gateway_ctx); 566 | } 567 | 568 | short DisC_gateway_ListenAndManage(DisC_session_t *session) 569 | { 570 | //NOTE this is the master function of the gateway and handling of it's events. All callbacks will be handled through 571 | //here and also heartbeating. 572 | internal_CheckSendHeartbeat(session); 573 | 574 | } 575 | 576 | //gateway event manage functions 577 | 578 | short DisC_gateway_SendEvent(DisC_session_t *session, short OP, unsigned char *data, unsigned long dataLen) 579 | { 580 | json_t *root = json_object(); 581 | 582 | json_object_set_new(root, "op", json_integer((int)OP)); 583 | json_object_set_new(root, "d", json_loads(data, 0, NULL));//need to check if the data is even json data 584 | 585 | //printf("Printevent: %s\n", json_dumps(root, JSON_INDENT(2))); 586 | 587 | internal_WriteData(session, json_dumps(root, 0), strlen(json_dumps(root, 0)), DISC_TRUE); 588 | 589 | json_decref(root); 590 | 591 | 592 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Sent event to gateway"); 593 | DisC_AddError(session, DISC_ERROR_NONE); 594 | return DISC_ERROR_NONE; 595 | } 596 | 597 | short DisC_gateway_BlockAndListenForEvent(DisC_session_t *session, DisC_gateway_payload_t **payload)//used to block and listen to gateway events 598 | { 599 | unsigned char *returnData = NULL, *finalData = NULL; 600 | unsigned long returnDataLen = 0, finalDataLen = 0; 601 | 602 | unsigned char finalFrame = 0, firstFrame = 1; 603 | 604 | unsigned long frameLen = 0, headerOffset = 0, frameDataOffset = 0; 605 | //125 126 127 606 | returnData = internal_ReadData(session, &returnDataLen, DISC_TRUE); 607 | while(returnData == NULL && (errno == EAGAIN || errno == EWOULDBLOCK) && !finalFrame)//need to not do this multiple times by doin g it in other functions. Will have to do later 608 | { 609 | //block until the ACK response 610 | returnData = internal_ReadData(session, &returnDataLen, DISC_TRUE); 611 | DisC_Delay(1000); 612 | 613 | //check for websocket frames 614 | if(firstFrame) 615 | { 616 | headerOffset = 0; 617 | //firstFrame = 0; 618 | } 619 | 620 | if(returnData[headerOffset] == 0b10000001) 621 | { 622 | finalFrame = 1; 623 | } 624 | else 625 | { 626 | finalFrame = 0; 627 | } 628 | 629 | if(returnData[headerOffset + 1] <= 125)//if its using that one little byte 630 | { 631 | frameLen = returnData[headerOffset + 1]; 632 | } 633 | else if(returnData[headerOffset + 1] == 126)//if its using a 16 bit number 634 | { 635 | frameLen = (unsigned long)*((uint16_t *)&returnData[headerOffset + 2]); 636 | frameDataOffset = sizeof(uint16_t) + 1; 637 | } 638 | else//if its using a 64bit number 639 | { 640 | frameLen = (unsigned long)*((uint64_t *)&returnData[headerOffset + 2]); 641 | frameDataOffset = sizeof(uint64_t) + 1; 642 | } 643 | 644 | finalDataLen += frameLen; 645 | finalData = realloc(finalData, finalDataLen); 646 | 647 | if(firstFrame) 648 | { 649 | memcpy(finalData, returnData + frameDataOffset, frameLen); 650 | firstFrame = 0; 651 | } 652 | else 653 | { 654 | memcpy(finalData + finalDataLen, returnData + frameDataOffset, frameLen); 655 | } 656 | 657 | 658 | 659 | 660 | } 661 | //returnData[returnDataLen] == 0x00; 662 | printf("returned gateway event: %s\n", finalData); 663 | 664 | //TODO use DisC_object_GenerateGatewayPayload 665 | //TODO future me: make an object parser for the "ready" event then listen for it in the listen and manage loop 666 | 667 | 668 | //*payload = DisC_object_GenerateGatewayPayload(session, finalData); 669 | 670 | DisC_AddError(session, DISC_ERROR_NONE); 671 | return DISC_ERROR_NONE; 672 | } 673 | 674 | 675 | short DisC_gateway_Identify(DisC_session_t *session, short large_threshold, short shardnum, short shardmax, DisC_gateway_status_update_t *status_update) 676 | { 677 | int returnval = 0; 678 | json_t *root = json_object(); 679 | json_t *properties = json_object(); 680 | json_t *presence = json_object(); 681 | json_t *game = json_object(); 682 | json_t *shards = json_array(); 683 | 684 | json_object_set_new(root, "token", json_string(session->token)); 685 | 686 | #ifdef _WIN32 687 | json_object_set_new(properties, "$os", json_string("windows")); 688 | if(session->logLevel == DISC_LOG_VERBOSE_AND_PRINT) 689 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Detected OS is windows"); 690 | #elif __APPLE__ 691 | json_object_set_new(properties, "$os", json_string("macOS")); 692 | if(session->logLevel == DISC_LOG_VERBOSE_AND_PRINT) 693 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Detected OS is mac OS"); 694 | #elif __linux__ 695 | json_object_set_new(properties, "$os", json_string("linux")); 696 | if(session->logLevel == DISC_LOG_VERBOSE_AND_PRINT) 697 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Detected OS is linux"); 698 | #elif __unix__ 699 | json_object_set_new(properties, "$os", json_string("unix")); 700 | if(session->logLevel == DISC_LOG_VERBOSE_AND_PRINT) 701 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Detected OS is unix"); 702 | #else 703 | json_object_set_new(properties, "$os", json_string("unknown")); 704 | if(session->logLevel == DISC_LOG_VERBOSE_AND_PRINT) 705 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Detected OS is unknown"); 706 | #endif 707 | json_object_set_new(properties, "$browser", json_string("DisC")); 708 | json_object_set_new(properties, "$device", json_string("DisC")); 709 | json_object_set_new(root, "properties", properties); 710 | 711 | json_object_set_new(root, "compress", json_boolean(DISC_FALSE));//TODO make a compression alternative option 712 | json_object_set_new(root, "large_threshold", json_integer((int)large_threshold)); 713 | if(shardmax > 0) 714 | { 715 | json_array_append_new(shards, json_integer(shardnum)); 716 | json_array_append_new(shards, json_integer(shardmax)); 717 | json_object_set_new(root, "shard", shards);//if this isnt necessary, then make it only run if requiring shards by user TODO this is the issue 718 | } 719 | 720 | 721 | if(status_update->game.type != DISC_ACTIVITY_NOTHING) 722 | { 723 | json_object_set_new(game, "name", json_string(status_update->game.name)); 724 | json_object_set_new(game, "type", json_integer(status_update->game.type)); 725 | if(status_update->game.url != NULL) 726 | json_object_set_new(game, "url", json_string(status_update->game.url)); 727 | json_object_set_new(presence, "game", game); 728 | } 729 | switch(status_update->status) 730 | { 731 | case DISC_STATUS_ONLINE: 732 | json_object_set_new(presence, "status", json_string("online")); 733 | break; 734 | case DISC_STATUS_DO_NOT_DISTURB: 735 | json_object_set_new(presence, "status", json_string("dnd")); 736 | break; 737 | case DISC_STATUS_IDLE: 738 | json_object_set_new(presence, "status", json_string("idle")); 739 | break; 740 | case DISC_STATUS_INVISIBLE: 741 | json_object_set_new(presence, "status", json_string("invisible")); 742 | break; 743 | case DISC_STATUS_OFFLINE: 744 | json_object_set_new(presence, "status", json_string("offline")); 745 | break; 746 | } 747 | if(status_update->since > 0) 748 | json_object_set_new(presence, "since", json_integer(status_update->since)); 749 | else 750 | json_object_set_new(presence, "since", NULL); 751 | json_object_set_new(presence, "afk", json_boolean(status_update->afk)); 752 | json_object_set_new(root, "presence", presence); 753 | 754 | //printf("printway: %s\n", json_dumps(root, JSON_INDENT(2))); 755 | 756 | //send the data to the gateway 757 | 758 | if(DisC_gateway_SendEvent(session, DISC_OP_IDENTIFY, json_dumps(root, 0), strlen(json_dumps(root, 0))) != DISC_ERROR_NONE) 759 | {//TODO before the rain knocks out power, I need to set this as an error case. This is a reminder for future me 760 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_ERROR, "Could not identify with gateway"); 761 | DisC_AddError(session, DISC_ERROR_CONNECTION); 762 | returnval = DISC_ERROR_CONNECTION; 763 | } 764 | else 765 | { 766 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Identified with gateway"); 767 | DisC_AddError(session, DISC_ERROR_NONE); 768 | returnval = DISC_ERROR_NONE; 769 | } 770 | 771 | json_decref(game); 772 | json_decref(presence); 773 | json_decref(properties); 774 | json_decref(root); 775 | 776 | return returnval; 777 | } 778 | -------------------------------------------------------------------------------- /src/DisC_object.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "DisC_types.h" 7 | #include "DisC_util.h" 8 | #include "DisC_errors.h" 9 | #include "DisC_object.h" 10 | 11 | DisC_gateway_payload_t *DisC_object_GenerateGatewayPayload(DisC_session_t *session, char *jsonData) 12 | { 13 | DisC_gateway_payload_t *payloadInternal = calloc(1, sizeof(DisC_gateway_payload_t)); 14 | json_t *root = NULL; 15 | json_t *data = NULL; 16 | 17 | 18 | if(payloadInternal == NULL) 19 | { 20 | exit(1); 21 | } 22 | 23 | if(jsonData != NULL) 24 | { 25 | root = json_loads(jsonData, 0, NULL); 26 | 27 | payloadInternal->op = json_integer_value(json_object_get(root, "op")); 28 | //detecting type 29 | if(json_is_object(json_object_get(root, "d"))) 30 | payloadInternal->dataType = DISC_EVENTDATA_TYPE_OBJECT; 31 | if(json_is_integer(json_object_get(root, "d"))) 32 | payloadInternal->dataType = DISC_EVENTDATA_TYPE_INTEGER; 33 | if(json_is_boolean(json_object_get(root, "d"))) 34 | payloadInternal->dataType = DISC_EVENTDATA_TYPE_BOOL; 35 | //================ 36 | payloadInternal->d = DisC_strmkdup(json_dumps(json_object_get(root, "d"), 0)); 37 | payloadInternal->s = json_integer_value(json_object_get(root, "s")); 38 | payloadInternal->t = DisC_strmkdup(json_string_value(json_object_get(root, "t"))); 39 | //do the sub object creation 40 | /* 41 | switch(payloadInternal->op) 42 | { 43 | case DISC_OP_DISPATCH: 44 | 45 | break; 46 | case DISC_OP_HEARTBEAT: 47 | 48 | break; 49 | case DISC_OP_IDENTIFY: 50 | 51 | break; 52 | case DISC_OP_STATUS_UPDATE: 53 | 54 | break; 55 | case DISC_OP_VOICE_STATUS_UPDATE: 56 | 57 | break; 58 | case DISC_OP_VOICE_SERVER_PING: 59 | 60 | break; 61 | case DISC_OP_RESUME: 62 | 63 | break; 64 | case DISC_OP_RECONNECT: 65 | 66 | break; 67 | case DISC_OP_REQUEST_GUILD_MEMBERS: 68 | 69 | break; 70 | case DISC_OP_INVALID_SESSION: 71 | 72 | break; 73 | case DISC_OP_HELLO: 74 | 75 | break; 76 | case DISC_OP_HEARTBEAT_ACK: 77 | 78 | break; 79 | } 80 | */ 81 | 82 | json_decref(root); 83 | 84 | return payloadInternal; 85 | } 86 | else 87 | { 88 | return NULL; 89 | } 90 | } 91 | 92 | DisC_channel_t *DisC_object_GenerateChannel(DisC_session_t *session, char *jsonData) 93 | { 94 | DisC_channel_t *channelInternal = calloc(1, sizeof(DisC_channel_t)); 95 | json_t *root = NULL; 96 | 97 | 98 | if(channelInternal == NULL) 99 | { 100 | exit(1); 101 | } 102 | 103 | if(jsonData != NULL) 104 | { 105 | root = json_loads(jsonData, 0, NULL); 106 | json_t *overwrites = json_object_get(root, "permission_overwrites"); 107 | json_t *recipients = json_object_get(root, "recipients"); 108 | 109 | channelInternal->id = DisC_strmkdup(json_string_value(json_object_get(root, "id"))); 110 | channelInternal->type = json_integer_value(json_object_get(root, "type")); 111 | channelInternal->guild_id = DisC_strmkdup(json_string_value(json_object_get(root, "guild_id"))); 112 | channelInternal->position = json_integer_value(json_object_get(root, "position")); 113 | //TODO OVERWRITE ARRAY OBJECTS 114 | if(overwrites != NULL) 115 | { 116 | channelInternal->permOverwritesCount = json_array_size(overwrites); 117 | DisC_overwrite_t *overwritesInternal = calloc(channelInternal->permOverwritesCount, sizeof(DisC_overwrite_t)); 118 | unsigned long i; 119 | for(i = 0; i < channelInternal->permOverwritesCount; i++) 120 | { 121 | DisC_overwrite_t *temp = DisC_object_GenerateOverwrite(session, json_dumps(json_array_get(overwrites, i), 0)); 122 | overwritesInternal[i] = *temp; 123 | } 124 | 125 | channelInternal->permission_overwrites = overwritesInternal; 126 | } 127 | else 128 | { 129 | channelInternal->permOverwritesCount = 0; 130 | } 131 | channelInternal->name = DisC_strmkdup(json_string_value(json_object_get(root, "name"))); 132 | channelInternal->topic = DisC_strmkdup(json_string_value(json_object_get(root, "topic"))); 133 | channelInternal->isNsfw = json_integer_value(json_object_get(root, "nsfw")); 134 | channelInternal->last_message_id = DisC_strmkdup(json_string_value(json_object_get(root, "last_message_id"))); 135 | channelInternal->bitrate = json_integer_value(json_object_get(root, "bitrate")); 136 | channelInternal->user_limit = json_integer_value(json_object_get(root, "user_limit")); 137 | //TODO recipients 138 | if(recipients != NULL) 139 | { 140 | channelInternal->recipientCount = json_array_size(recipients); 141 | DisC_user_t *recipientsInternal = calloc(channelInternal->recipientCount, sizeof(DisC_user_t)); 142 | unsigned long i; 143 | for(i = 0; i < channelInternal->recipientCount; i++) 144 | { 145 | DisC_user_t *temp = DisC_object_GenerateUser(session, json_dumps(json_array_get(recipients, i), 0));//nononono, this is json_string_value 146 | recipientsInternal[i] = *temp; 147 | //free(temp);//actually do it right 148 | } 149 | 150 | channelInternal->recipients = recipientsInternal; 151 | } 152 | else 153 | { 154 | channelInternal->recipientCount = 0; 155 | } 156 | channelInternal->icon = DisC_strmkdup(json_string_value(json_object_get(root, "icon"))); 157 | channelInternal->owner_id = DisC_strmkdup(json_string_value(json_object_get(root, "owner_id"))); 158 | channelInternal->application_id = DisC_strmkdup(json_string_value(json_object_get(root, "application_id"))); 159 | channelInternal->parent_id = DisC_strmkdup(json_string_value(json_object_get(root, "parent_id"))); 160 | channelInternal->last_pin_timestamp = DisC_strmkdup(json_string_value(json_object_get(root, "last_pin_timestamp"))); 161 | 162 | json_decref(root); 163 | 164 | return channelInternal; 165 | } 166 | else 167 | { 168 | return NULL; 169 | } 170 | } 171 | 172 | DisC_message_t *DisC_object_GenerateMessage(DisC_session_t *session, char *jsonData) 173 | { 174 | DisC_message_t *messageInternal = calloc(1, sizeof(DisC_message_t)); 175 | json_t *root = NULL; 176 | json_t *mentions = NULL; 177 | json_t *roles = NULL; 178 | json_t *attachments = NULL; 179 | json_t *embeds = NULL; 180 | json_t *reactions = NULL; 181 | 182 | 183 | if(messageInternal == NULL) 184 | { 185 | exit(1); 186 | } 187 | 188 | if(jsonData != NULL) 189 | { 190 | root = json_loads(jsonData, 0, NULL); 191 | DisC_user_t *account = NULL; 192 | mentions = json_object_get(root, "mentions"); 193 | roles = json_object_get(root, "mention_roles"); 194 | attachments = json_object_get(root, "attachments"); 195 | embeds = json_object_get(root, "embeds"); 196 | reactions = json_object_get(root, "reactions"); 197 | 198 | messageInternal->id = DisC_strmkdup(json_string_value(json_object_get(root, "id"))); 199 | messageInternal->channel_id = DisC_strmkdup(json_string_value(json_object_get(root, "channel_id"))); 200 | 201 | account = DisC_object_GenerateUser(session, json_dumps(json_object_get(root, "author"), 0)); 202 | messageInternal->author = *account; 203 | 204 | messageInternal->content = DisC_strmkdup(json_string_value(json_object_get(root, "content"))); 205 | messageInternal->timestamp = DisC_strmkdup(json_string_value(json_object_get(root, "timestamp"))); 206 | messageInternal->edited_timestamp = DisC_strmkdup(json_string_value(json_object_get(root, "edited_timestamp"))); 207 | messageInternal->tts = json_boolean_value(json_object_get(root, "tts")); 208 | messageInternal->mention_everyone = json_boolean_value(json_object_get(root, "mention_everyone")); 209 | //=============//TODO fix. This wasnt working 210 | if(mentions != NULL) 211 | { 212 | //printf("%s\n", json_dumps(mentions, JSON_INDENT(2))); 213 | messageInternal->mentionCount = json_array_size(mentions); 214 | DisC_user_t *mentionsInternal = calloc(messageInternal->mentionCount, sizeof(DisC_user_t)); 215 | unsigned long i; 216 | for(i = 0; i < messageInternal->mentionCount; i++) 217 | { 218 | DisC_user_t *temp = DisC_object_GenerateUser(session, json_dumps(json_array_get(mentions, i), 0)); 219 | mentionsInternal[i] = *temp; 220 | //free(temp);//actually do it right 221 | } 222 | 223 | messageInternal->mentions = mentionsInternal; 224 | } 225 | else 226 | { 227 | messageInternal->mentionCount = 0; 228 | } 229 | 230 | if(roles != NULL)//TODO fix this. It segfaults on access 231 | { 232 | //printf("%s\n", json_dumps(roles, JSON_INDENT(2))); 233 | messageInternal->roleCount = json_array_size(roles); 234 | messageInternal->roles = calloc(messageInternal->roleCount, DISC_SNOWFLAKE_ALLOCSIZE * sizeof(char)); 235 | unsigned long i; 236 | for(i = 0; i < messageInternal->roleCount; i++) 237 | { 238 | messageInternal->roles[i] = DisC_strmkdup(json_string_value(json_array_get(roles, i))); 239 | //free(temp);//actually do it right 240 | } 241 | //if(messageInternal->roleCount> 1) 242 | //{ 243 | //printf("%s\n%s\n", messageInternal->roles[0], messageInternal->roles[1]); 244 | //} 245 | } 246 | else 247 | { 248 | messageInternal->roleCount = 0; 249 | } 250 | 251 | if(attachments != NULL) 252 | { 253 | //printf("%s\n", json_dumps(mentions, JSON_INDENT(2))); 254 | messageInternal->attachmentCount = json_array_size(attachments); 255 | DisC_attachment_t *attachmentInternal = calloc(messageInternal->attachmentCount, sizeof(DisC_attachment_t)); 256 | unsigned long i; 257 | for(i = 0; i < messageInternal->attachmentCount; i++) 258 | { 259 | DisC_attachment_t *temp = DisC_object_GenerateAttachment(session, json_dumps(json_array_get(attachments, i), 0)); 260 | attachmentInternal[i] = *temp; 261 | //free(temp);//actually do it right 262 | } 263 | 264 | messageInternal->attatchments = attachmentInternal; 265 | } 266 | else 267 | { 268 | messageInternal->attachmentCount = 0; 269 | } 270 | 271 | if(embeds != NULL) 272 | { 273 | //printf("%s\n", json_dumps(mentions, JSON_INDENT(2))); 274 | messageInternal->embedCount = json_array_size(embeds); 275 | DisC_embed_t *embedInternal = calloc(messageInternal->embedCount, sizeof(DisC_embed_t)); 276 | unsigned long i; 277 | for(i = 0; i < messageInternal->embedCount; i++) 278 | { 279 | DisC_embed_t *temp = DisC_object_GenerateEmbed(session, json_dumps(json_array_get(embeds, i), 0)); 280 | embedInternal[i] = *temp; 281 | //free(temp);//actually do it right 282 | } 283 | 284 | messageInternal->embeds = embedInternal; 285 | } 286 | else 287 | { 288 | messageInternal->embedCount = 0; 289 | } 290 | 291 | if(reactions != NULL) 292 | { 293 | //printf("%s\n", json_dumps(mentions, JSON_INDENT(2))); 294 | messageInternal->reactionCount = json_array_size(reactions); 295 | DisC_reaction_t *reactionInternal = calloc(messageInternal->reactionCount, sizeof(DisC_reaction_t)); 296 | unsigned long i; 297 | for(i = 0; i < messageInternal->reactionCount; i++) 298 | { 299 | DisC_reaction_t *temp = DisC_object_GenerateReaction(session, json_dumps(json_array_get(reactions, i), 0)); 300 | reactionInternal[i] = *temp; 301 | //free(temp);//actually do it right 302 | } 303 | 304 | messageInternal->reactions = reactionInternal; 305 | } 306 | else 307 | { 308 | messageInternal->reactionCount = 0; 309 | } 310 | 311 | 312 | messageInternal->nonce = DisC_strmkdup(json_string_value(json_object_get(root, "nonce"))); 313 | messageInternal->pinned = json_boolean_value(json_object_get(root, "pinned")); 314 | messageInternal->webhook_id = DisC_strmkdup(json_string_value(json_object_get(root, "webhook_id"))); 315 | messageInternal->type = json_integer_value(json_object_get(root, "type")); 316 | 317 | json_decref(root); 318 | 319 | return messageInternal; 320 | } 321 | else 322 | { 323 | return NULL; 324 | } 325 | } 326 | 327 | DisC_user_t *DisC_object_GenerateUser(DisC_session_t *session, char *jsonData) 328 | { 329 | DisC_user_t *userInternal = calloc(1, sizeof(DisC_user_t)); 330 | json_t *root = NULL; 331 | 332 | 333 | if(userInternal == NULL) 334 | { 335 | exit(1); 336 | } 337 | 338 | if(jsonData != NULL) 339 | { 340 | root = json_loads(jsonData, 0, NULL); 341 | 342 | 343 | userInternal->id = DisC_strmkdup(json_string_value(json_object_get(root, "id"))); 344 | userInternal->username = DisC_strmkdup(json_string_value(json_object_get(root, "username"))); 345 | userInternal->discriminator = DisC_strmkdup(json_string_value(json_object_get(root, "discriminator"))); 346 | userInternal->avatar = DisC_strmkdup(json_string_value(json_object_get(root, "avatar"))); 347 | userInternal->bot = json_boolean_value(json_object_get(root, "bot")); 348 | userInternal->mfa_enabled = json_boolean_value(json_object_get(root, "mfa_enabled")); 349 | userInternal->verified = json_boolean_value(json_object_get(root, "verified")); 350 | userInternal->email = DisC_strmkdup(json_string_value(json_object_get(root, "email"))); 351 | 352 | json_decref(root); 353 | 354 | return userInternal; 355 | } 356 | else 357 | { 358 | return NULL; 359 | } 360 | } 361 | 362 | DisC_overwrite_t *DisC_object_GenerateOverwrite(DisC_session_t *session, char *jsonData) 363 | { 364 | DisC_overwrite_t *overwriteInternal = calloc(1, sizeof(DisC_overwrite_t)); 365 | json_t *root = NULL; 366 | 367 | 368 | if(overwriteInternal == NULL) 369 | { 370 | exit(1); 371 | } 372 | 373 | if(jsonData != NULL) 374 | { 375 | root = json_loads(jsonData, 0, NULL); 376 | 377 | overwriteInternal->id = DisC_strmkdup(json_string_value(json_object_get(root, "id"))); 378 | overwriteInternal->type = DisC_strmkdup(json_string_value(json_object_get(root, "type"))); 379 | overwriteInternal->allow = json_integer_value(json_object_get(root, "allow")); 380 | overwriteInternal->deny = json_integer_value(json_object_get(root, "deny")); 381 | 382 | json_decref(root); 383 | return overwriteInternal; 384 | } 385 | else 386 | { 387 | return NULL; 388 | } 389 | } 390 | 391 | DisC_role_t *DisC_object_GenerateRole(DisC_session_t *session, char *jsonData) 392 | { 393 | DisC_role_t *roleInternal = calloc(1, sizeof(DisC_role_t)); 394 | json_t *root = NULL; 395 | 396 | if(roleInternal == NULL) 397 | { 398 | exit(1); 399 | } 400 | 401 | if(jsonData != NULL) 402 | { 403 | root = json_loads(jsonData, 0, NULL); 404 | 405 | roleInternal->id = DisC_strmkdup(json_string_value(json_object_get(root, "id"))); 406 | roleInternal->name = DisC_strmkdup(json_string_value(json_object_get(root, "name"))); 407 | roleInternal->color = json_integer_value(json_object_get(root, "color")); 408 | roleInternal->hoist = json_boolean_value(json_object_get(root, "hoist")); 409 | roleInternal->position = json_integer_value(json_object_get(root, "position")); 410 | roleInternal->permissions = json_integer_value(json_object_get(root, "permissions")); 411 | roleInternal->managed = json_boolean_value(json_object_get(root, "managed")); 412 | roleInternal->mentionable = json_boolean_value(json_object_get(root, "mentionable")); 413 | 414 | json_decref(root); 415 | 416 | return roleInternal; 417 | } 418 | else 419 | { 420 | return NULL; 421 | } 422 | } 423 | 424 | DisC_attachment_t *DisC_object_GenerateAttachment(DisC_session_t *session, char *jsonData) 425 | { 426 | DisC_attachment_t *attachmentInternal = calloc(1, sizeof(DisC_attachment_t)); 427 | json_t *root = NULL; 428 | 429 | 430 | if(attachmentInternal == NULL) 431 | { 432 | exit(1); 433 | } 434 | 435 | if(jsonData != NULL) 436 | { 437 | root = json_loads(jsonData, 0, NULL); 438 | 439 | attachmentInternal->id = DisC_strmkdup(json_string_value(json_object_get(root, "id"))); 440 | attachmentInternal->filename = DisC_strmkdup(json_string_value(json_object_get(root, "filename"))); 441 | attachmentInternal->size = json_integer_value(json_object_get(root, "size")); 442 | attachmentInternal->url = DisC_strmkdup(json_string_value(json_object_get(root, "url"))); 443 | attachmentInternal->proxy_url = DisC_strmkdup(json_string_value(json_object_get(root, "proxy_url"))); 444 | attachmentInternal->height = json_integer_value(json_object_get(root, "height")); 445 | attachmentInternal->width = json_integer_value(json_object_get(root, "width")); 446 | 447 | 448 | json_decref(root); 449 | 450 | return attachmentInternal; 451 | } 452 | else 453 | { 454 | return NULL; 455 | } 456 | } 457 | 458 | DisC_embed_t *DisC_object_GenerateEmbed(DisC_session_t *session, char *jsonData) 459 | { 460 | DisC_embed_t *embedInternal = calloc(1, sizeof(DisC_embed_t)); 461 | json_t *root = NULL; 462 | json_t *footer = NULL; 463 | json_t *image = NULL; 464 | json_t *thumbnail = NULL; 465 | json_t *video = NULL; 466 | json_t *provider = NULL; 467 | json_t *author = NULL; 468 | json_t *fields = NULL; 469 | 470 | 471 | if(embedInternal == NULL) 472 | { 473 | exit(1); 474 | } 475 | 476 | if(jsonData != NULL) 477 | { 478 | root = json_loads(jsonData, 0, NULL); 479 | footer = json_object_get(root, "footer"); 480 | image = json_object_get(root, "image"); 481 | thumbnail = json_object_get(root, "thumbnail"); 482 | video = json_object_get(root, "video"); 483 | provider = json_object_get(root, "provider"); 484 | author = json_object_get(root, "author"); 485 | fields = json_object_get(root, "fields"); 486 | 487 | embedInternal->title = DisC_strmkdup(json_string_value(json_object_get(root, "title"))); 488 | embedInternal->type = DisC_strmkdup(json_string_value(json_object_get(root, "type"))); 489 | embedInternal->description = DisC_strmkdup(json_string_value(json_object_get(root, "description"))); 490 | embedInternal->url = DisC_strmkdup(json_string_value(json_object_get(root, "url"))); 491 | embedInternal->timestamp = DisC_strmkdup(json_string_value(json_object_get(root, "timestamp"))); 492 | embedInternal->color = json_integer_value(json_object_get(root, "color")); 493 | //TODO footer 494 | //TODO image 495 | //TODO thumbnail 496 | //TODO video 497 | //TODO provider 498 | //TODO author 499 | //TODO field array 500 | 501 | json_decref(root); 502 | 503 | return embedInternal; 504 | } 505 | else 506 | { 507 | return NULL; 508 | } 509 | } 510 | 511 | DisC_reaction_t *DisC_object_GenerateReaction(DisC_session_t *session, char *jsonData) 512 | { 513 | DisC_reaction_t *reactionInternal = calloc(1, sizeof(DisC_reaction_t)); 514 | json_t *root = NULL; 515 | 516 | 517 | if(reactionInternal == NULL) 518 | { 519 | exit(1); 520 | } 521 | 522 | if(jsonData != NULL) 523 | { 524 | root = json_loads(jsonData, 0, NULL); 525 | 526 | reactionInternal->count = json_integer_value(json_object_get(root, "count")); 527 | reactionInternal->me = json_boolean_value(json_object_get(root, "me")); 528 | //TODO emoji 529 | 530 | json_decref(root); 531 | 532 | return reactionInternal; 533 | } 534 | else 535 | { 536 | return NULL; 537 | } 538 | } 539 | -------------------------------------------------------------------------------- /src/DisC_rest.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "DisC_types.h" 13 | #include "DisC_util.h" 14 | #include "DisC_errors.h" 15 | #include "DisC_object.h" 16 | #include "DisC_rest.h" 17 | 18 | //TODO make an actual REST request function 19 | 20 | static char *internal_UnChunk(DisC_session_t *session, char *str); 21 | 22 | static char *internal_GetHTTPResponseCode(DisC_session_t *session, char *str); 23 | 24 | static char *internal_GenerateHeaders(DisC_session_t *session, char *url, char *httpAction, const char *contentType, unsigned long contentLength); 25 | 26 | static unsigned char *internal_ReadData(DisC_session_t *session, unsigned long *dataLen); 27 | 28 | static short internal_WriteData(DisC_session_t *session, unsigned char *data, unsigned long dataLen); 29 | 30 | //====================================================================================== 31 | 32 | static char *internal_UnChunk(DisC_session_t *session, char *str) 33 | { 34 | char *returnStr = NULL; 35 | char *returnStrFinal = NULL; 36 | char *start = strstr(str, "\r\n\r\n"); 37 | start += strlen("\r\n\r\n"); 38 | char *scanStr = DisC_strmkdup(start); 39 | char *chunkToken = scanStr; 40 | DisC_BOOL_t foundChunkLen = DISC_FALSE; 41 | long chunkLen = 0; 42 | long chunkCount = 0; 43 | DisC_BOOL_t canExit = 0; 44 | 45 | 46 | if(strstr(str, "Transfer-Encoding") != NULL) 47 | { 48 | strtok(chunkToken, "\r\n"); 49 | //printf("Token: %s\n", chunkToken); 50 | chunkLen = strtol(chunkToken, NULL, 16); 51 | foundChunkLen = DISC_TRUE; 52 | 53 | 54 | if(chunkLen > 0) 55 | { 56 | //returnStr = malloc(size_t __size) 57 | while((chunkToken = strtok(NULL, "\r\n")) != NULL && !canExit) 58 | { 59 | if(foundChunkLen)//if it found the num of chunk length, set up the body stuff 60 | { 61 | if((returnStr = realloc(returnStr, (chunkCount + chunkLen) * sizeof(char))) != NULL) 62 | { 63 | memmove(returnStr + chunkCount, chunkToken, chunkLen); 64 | chunkCount += chunkLen; 65 | foundChunkLen = DISC_FALSE; 66 | } 67 | else 68 | exit(1); 69 | } 70 | else 71 | { 72 | chunkLen = strtol(chunkToken, NULL, 16); 73 | if(chunkLen == 0) 74 | { 75 | canExit = 1; 76 | } 77 | foundChunkLen = DISC_TRUE; 78 | } 79 | //printf("Token: %s\n", chunkToken); 80 | } 81 | 82 | returnStrFinal = calloc(chunkCount + 1, sizeof(char)); 83 | if(returnStrFinal == NULL) 84 | exit(1); 85 | 86 | memmove(returnStrFinal, returnStr, chunkCount); 87 | } 88 | 89 | //printf("Final: %s\n", returnStrFinal); 90 | free(scanStr); 91 | return returnStrFinal; 92 | } 93 | else 94 | { 95 | free(scanStr); 96 | return NULL; 97 | } 98 | 99 | 100 | } 101 | 102 | static char *internal_GetHTTPResponseCode(DisC_session_t *session, char *str) 103 | { 104 | //make a temp string to verify the send 105 | //temporary fix for chunked response still sending data that code later mistakes for the end of headers 106 | if(*str == '0') 107 | { 108 | if(strstr(str, "\r\n\r\n") == &str[1]) 109 | { 110 | str[1] = "eror"; 111 | } 112 | } 113 | char *tempReturn = NULL; 114 | DisC_asprintf(&tempReturn, "%s", strstr(str, "HTTP/1.1 ")); 115 | char *httpStatus = strtok(tempReturn, "\r\n"); 116 | char *code = httpStatus + strlen("HTTP/1.1 "); 117 | strtok(code, " "); 118 | //printf("str: %s\ncode: %s\nand: %s\ncode: %s\n", str, httpStatus, tempReturn, code); 119 | //finish getting status 120 | 121 | char *returnStr = DisC_strmkdup(code); 122 | free(tempReturn); 123 | 124 | return returnStr; 125 | } 126 | 127 | static char *internal_GenerateHeaders(DisC_session_t *session, char *url, char *httpAction, const char *contentType, unsigned long contentLength) 128 | { 129 | char *final = NULL; 130 | char *base = "%s %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\nAuthorization: %s%s\r\nUser-Agent: (so sorry for not obeying rate limiting. Nearly at the point that I can add it)DisC v%d.%d.%d: %s\r\n%s\r\n%s"; 131 | 132 | if(strcmp(httpAction, "GET") == 0 || strcmp(httpAction, "DELETE") == 0) 133 | { 134 | //otherwise just set the %s to nothing 135 | DisC_asprintf(&final, base, httpAction, url, DISC_REST_HOST, session->clientType ? "Bot " : "", session->token, DISC_VER_MAJOR, DISC_VER_MINOR, DISC_VER_REVISION, session->internalName, "", ""); 136 | } 137 | else 138 | { 139 | //add a mime type and content length: 140 | //if(strcmp(httpAction, "GETQS") == 0) 141 | //{ 142 | //DisC_asprintf(&final, base, "GET", url, host, session->clientType ? "Bot " : "", session->token, DISC_VER_MAJOR, DISC_VER_MINOR, DISC_VER_REVISION, session->internalName, "Content-Type: %s\r\nContent-Length: %lu\r\n", "%s"); 143 | //} 144 | //else 145 | //{ 146 | DisC_asprintf(&final, base, httpAction, url, DISC_REST_HOST, session->clientType ? "Bot " : "", session->token, DISC_VER_MAJOR, DISC_VER_MINOR, DISC_VER_REVISION, session->internalName, "Content-Type: %s\r\nContent-Length: %lu\r\n", "%s"); 147 | //} 148 | 149 | //printf("%s\n", final); 150 | DisC_asprintf(&final, final, contentType, contentLength, "%s"); 151 | //printf("%s\n", final); 152 | } 153 | 154 | return final; 155 | } 156 | 157 | static unsigned char *internal_ReadData(DisC_session_t *session, unsigned long *dataLen) 158 | { 159 | char *buffer = NULL; 160 | unsigned long byteCount = 0; 161 | unsigned long byteChunkMax = 1024; 162 | unsigned long contentLength = 0; 163 | unsigned long status; 164 | int firstRun = 0; 165 | int isChunked = 2; 166 | int chunkedCanExit = 0; 167 | 168 | buffer = malloc(byteChunkMax); 169 | if(buffer == NULL) 170 | { 171 | exit(1); 172 | } 173 | status = byteChunkMax; 174 | do { 175 | status = (unsigned long)BIO_read(session->DONOTSET_rest_bio, buffer + byteCount, status); 176 | if(status == -1 && firstRun == 0) 177 | { 178 | return NULL; 179 | } 180 | else if(firstRun == 0) 181 | { 182 | firstRun++; 183 | } 184 | 185 | byteCount += status; 186 | 187 | buffer = realloc(buffer, byteCount + byteChunkMax); 188 | //printf("%d\n", status); 189 | if(strstr(buffer, "Server: ") != NULL && isChunked == 2) 190 | { 191 | //printf("Found Type\n"); 192 | if(strstr(buffer, "Transfer-Encoding: chunked")!= NULL) 193 | { 194 | //printf("found chunked\n"); 195 | isChunked = 1; 196 | } 197 | else if(strstr(buffer, "Content-Length")) 198 | { 199 | //printf("found contentlength\n"); 200 | isChunked = 0; 201 | 202 | char *tempStr = NULL; 203 | DisC_asprintf(&tempStr, "%s", buffer); 204 | 205 | char *tempPtr = strstr(tempStr, "Content-Length"); 206 | tempPtr = strtok(tempPtr, "\r\n"); 207 | 208 | tempPtr += strlen("Content-Length: "); 209 | 210 | 211 | contentLength = (unsigned long)strtol(tempPtr, NULL, 10); 212 | //printf("thing: %lu\n", contentLength); 213 | free(tempStr); 214 | } 215 | } 216 | 217 | //printf("buffer: %s\nstatus: %lu", buffer, status); 218 | 219 | if(isChunked == 1) 220 | { 221 | //printf("Detecting chunk...\n"); 222 | char *tempScanBuffer = calloc(byteCount + 1, sizeof(char)); 223 | memmove(tempScanBuffer, buffer, byteCount); 224 | if(strstr(tempScanBuffer, "\r\n0\r\n\r\n")) 225 | { 226 | //BIO_read(session->DONOTSET_rest_bio, NULL, strlen("0\r\n\r\n")); 227 | //printf("Found end of chunk: %d\n", status); 228 | chunkedCanExit = 1; 229 | } 230 | free(tempScanBuffer); 231 | } 232 | else if(contentLength > 0) 233 | { 234 | if(strstr(buffer, "\r\n\r\n") != NULL) 235 | { 236 | char *tempScanBuffer = calloc(byteCount + 1, sizeof(char)); 237 | memmove(tempScanBuffer, buffer, byteCount); 238 | //printf("found end of headers\n"); 239 | char *marked = strstr(tempScanBuffer, "\r\n\r\n"); 240 | unsigned long i; 241 | for(i = 0; i < byteCount; i++) 242 | { 243 | if(&tempScanBuffer[i] == marked) 244 | { 245 | 246 | i += strlen("\r\n\r\n"); 247 | 248 | if(byteCount - i == contentLength) 249 | { 250 | chunkedCanExit = 1;//reusing a variable 251 | //printf("can exit\n"); 252 | } 253 | else 254 | { 255 | //printf("cant: %lu\n", byteCount - i); 256 | } 257 | 258 | 259 | } 260 | } 261 | free(tempScanBuffer); 262 | } 263 | } 264 | 265 | } while(status > 0 && chunkedCanExit == 0); 266 | 267 | 268 | //buffer[byteCount] = '\0';//not always going to be text data. Set in rest functions 269 | 270 | buffer = realloc(buffer, byteCount); 271 | 272 | *dataLen = byteCount; 273 | return buffer; 274 | } 275 | 276 | static short internal_WriteData(DisC_session_t *session, unsigned char *data, unsigned long dataLen) 277 | { 278 | while(BIO_write(session->DONOTSET_rest_bio, data, dataLen) <= 0) 279 | { 280 | short result = DisC_REST_ReconnectSession(session);//reconnect if necessary, then just return if it couldnt reconnect 281 | if(result) 282 | { 283 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_ERROR, "Could not retry or reconnect after attempting to send REST data"); 284 | DisC_AddError(session, DISC_ERROR_NONE); 285 | return DISC_ERROR_NONE; 286 | } 287 | } 288 | //printf("writing:\n\n\n%s\n\n\n", data); 289 | //DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Wrote data", data); 290 | DisC_AddError(session, DISC_ERROR_NONE); 291 | return DISC_ERROR_NONE; 292 | } 293 | 294 | //public functions 295 | 296 | short DisC_REST_Init() 297 | { 298 | SSL_load_error_strings(); 299 | SSL_library_init(); 300 | ERR_load_BIO_strings(); 301 | OpenSSL_add_all_algorithms(); 302 | 303 | return DISC_ERROR_NONE; 304 | } 305 | 306 | short DisC_REST_ReconnectSession(DisC_session_t *session) 307 | { 308 | unsigned long reconnNum = 0; 309 | while(DisC_REST_InitSession(session) != DISC_ERROR_NONE) 310 | { 311 | if(DISC_MAX_RECONNECTS != -1) 312 | { 313 | reconnNum++; 314 | } 315 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Reconnecting to %s..", DISC_REST_HOST); 316 | sleep(DISC_RECONNECT_DELAY); 317 | if(reconnNum == DISC_MAX_RECONNECTS) 318 | { 319 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Could not reconnect"); 320 | DisC_AddError(session, DISC_ERROR_CONNECTION); 321 | return DISC_ERROR_CONNECTION; 322 | } 323 | } 324 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Reconnected"); 325 | DisC_AddError(session, DISC_ERROR_NONE); 326 | return DISC_ERROR_NONE; 327 | //fix state stuff if needed 328 | } 329 | 330 | short DisC_REST_InitSession(DisC_session_t *session)//TODO set a timeout 331 | { 332 | //set the session ssl context to TLS 1.2 333 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Connecting to %s...", DISC_REST_HOST); 334 | session->DONOTSET_rest_ctx = SSL_CTX_new(TLSv1_2_client_method()); 335 | 336 | 337 | session->DONOTSET_rest_bio = BIO_new_ssl_connect(session->DONOTSET_rest_ctx); 338 | BIO_set_conn_hostname(session->DONOTSET_rest_bio, DISC_REST_HOSTNAME); 339 | 340 | //connect 341 | if(BIO_do_connect(session->DONOTSET_rest_bio) <= 0) 342 | { 343 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_ERROR, "Could not connect"); 344 | DisC_REST_ReconnectSession(session); 345 | DisC_AddError(session, DISC_ERROR_CONNECTION); 346 | return DISC_ERROR_CONNECTION; 347 | } 348 | else 349 | { 350 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Connected"); 351 | DisC_AddError(session, DISC_ERROR_NONE); 352 | return DISC_ERROR_NONE; 353 | } 354 | } 355 | 356 | short DisC_REST_DestroySession(DisC_session_t *session) 357 | { 358 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Disconnecting from %s...", DISC_REST_HOST); 359 | BIO_free_all(session->DONOTSET_rest_bio); 360 | SSL_CTX_free(session->DONOTSET_rest_ctx); 361 | 362 | DisC_AddError(session, DISC_ERROR_NONE); 363 | return DISC_ERROR_NONE; 364 | } 365 | 366 | short DisC_REST_DiscordHTTPRequest(DisC_session_t *session, char **returnBody, unsigned long *returnBodyLen, char **returnCode, char *method, char *mime, char *URI, char *data, unsigned long datalen) 367 | { 368 | char *request = NULL; 369 | char *recieve = NULL; 370 | char *callocRequest = NULL; 371 | 372 | unsigned long internalRecieveLen = 0; 373 | 374 | if(strcmp(method, "GET") == 0 && strcmp(method, "DELETE") == 0) 375 | { 376 | request = internal_GenerateHeaders(session, URI, method, NULL, 0); 377 | } 378 | else 379 | { 380 | request = internal_GenerateHeaders(session, URI, method, mime, datalen); 381 | DisC_asprintf(&request, request, data); 382 | } 383 | 384 | do 385 | { 386 | internal_WriteData(session, request, strlen(request)); 387 | recieve = internal_ReadData(session, &internalRecieveLen); 388 | } 389 | while(recieve == NULL); 390 | 391 | callocRequest = malloc(internalRecieveLen + 1);//turn it into a string 392 | if(callocRequest == NULL) 393 | { 394 | exit(1); 395 | } 396 | memmove(callocRequest, recieve, internalRecieveLen); 397 | 398 | *returnBody = internal_UnChunk(session, callocRequest); 399 | 400 | *returnCode = internal_GetHTTPResponseCode(session, callocRequest); 401 | 402 | *returnBodyLen = internalRecieveLen; 403 | 404 | if(*returnBody == NULL)//then its content length 405 | { 406 | *returnBody = DisC_strmkdup(strstr(callocRequest, "\r\n\r\n") + strlen("\r\n\r\n")); 407 | } 408 | 409 | 410 | 411 | 412 | free(request); 413 | free(recieve); 414 | free(callocRequest); 415 | 416 | DisC_AddError(session, DISC_ERROR_NONE); 417 | return DISC_ERROR_NONE; 418 | } 419 | 420 | //============================================================================================================================================= 421 | 422 | short DisC_REST_GetChannel(DisC_session_t *session, DisC_snowflake_t channelId, DisC_channel_t **channel) 423 | { 424 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Getting channel"); 425 | short returnval; 426 | char *url = NULL; 427 | char *code = NULL; 428 | char *readData = NULL; 429 | unsigned long readDataLen = 0; 430 | DisC_channel_t *channelInternal = NULL; 431 | 432 | DisC_asprintf(&url, "%s/channels/%s", DISC_REST_APIBASE, channelId); 433 | DisC_REST_DiscordHTTPRequest(session, &readData, &readDataLen, &code, "GET", NULL, url, NULL, 0); 434 | 435 | json_t *retRoot = NULL; 436 | 437 | 438 | //============================================ 439 | 440 | if(strcmp(code, "200") == 0) 441 | { 442 | channelInternal = DisC_object_GenerateChannel(session, readData); 443 | 444 | if(channelInternal != NULL) 445 | { 446 | *channel = channelInternal; 447 | 448 | DisC_AddError(session, DISC_ERROR_NONE); 449 | returnval = DISC_ERROR_NONE; 450 | } 451 | else 452 | { 453 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_ERROR, "Failed to parse JSON"); 454 | DisC_AddError(session, DISC_ERROR_MEMORY); 455 | returnval = DISC_ERROR_MEMORY; 456 | } 457 | 458 | } 459 | else if(strcmp(code, "401") == 0) 460 | { 461 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "Insufficient permissions for action"); 462 | DisC_AddError(session, DISC_ERROR_PERMISSIONS); 463 | returnval = DISC_ERROR_PERMISSIONS; 464 | } 465 | else{ 466 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "HTTP error: %s: %s", code, readData); 467 | DisC_AddError(session, DISC_ERROR_GENERIC); 468 | returnval = DISC_ERROR_GENERIC; 469 | } 470 | 471 | 472 | free(code); 473 | free(readData); 474 | free(url); 475 | 476 | return returnval; 477 | } 478 | 479 | short DisC_REST_ModifyChannel(DisC_session_t *session, DisC_snowflake_t channelId, char *name, int position, char *topic, DisC_BOOL_t nsfw, int bitrate, int userLimit, DisC_overwrite_t *overwrites, DisC_snowflake_t parentId) 480 | { 481 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Modifying channel"); 482 | short returnval; 483 | char *url = NULL; 484 | char *code = NULL; 485 | char *readData = NULL; 486 | unsigned long readDataLen = 0; 487 | 488 | 489 | json_t *root = json_object(); 490 | 491 | if(name != NULL) 492 | json_object_set_new(root, "name", json_string(name)); 493 | 494 | if(position >= 0) 495 | json_object_set_new(root, "position", json_integer(position)); 496 | 497 | if(topic != NULL) 498 | json_object_set_new(root, "topic", json_string(topic)); 499 | 500 | if(nsfw >= 0) 501 | json_object_set_new(root, "nsfw", json_boolean(nsfw)); 502 | 503 | if(bitrate >= 0) 504 | json_object_set_new(root, "bitrate", json_integer(bitrate)); 505 | 506 | if(userLimit >= 0) 507 | json_object_set_new(root, "user_limit", json_integer(userLimit)); 508 | 509 | if(overwrites != NULL) 510 | json_object_set_new(root, "permission_overwrites", json_string("test")); 511 | 512 | if(parentId != NULL) 513 | json_object_set_new(root, "parent_id", json_string(parentId)); 514 | 515 | //=================================================================== 516 | 517 | DisC_asprintf(&url, "%s/channels/%s", DISC_REST_APIBASE, channelId); 518 | DisC_REST_DiscordHTTPRequest(session, &readData, &readDataLen, &code, "PUT", "application/json", url, json_dumps(root, 0), strlen(json_dumps(root, 0))); 519 | 520 | 521 | //============================================ 522 | 523 | if(strcmp(code, "200") == 0) 524 | { 525 | DisC_AddError(session, DISC_ERROR_NONE); 526 | returnval = DISC_ERROR_NONE; 527 | } 528 | else if(strcmp(code, "401") == 0) 529 | { 530 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "Insufficient permissions for action"); 531 | DisC_AddError(session, DISC_ERROR_PERMISSIONS); 532 | returnval = DISC_ERROR_PERMISSIONS; 533 | } 534 | else{ 535 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "HTTP error: %s: %s", code, readData); 536 | DisC_AddError(session, DISC_ERROR_GENERIC); 537 | returnval = DISC_ERROR_GENERIC; 538 | } 539 | 540 | //free everything================================================== 541 | json_decref(root); 542 | free(code); 543 | free(url); 544 | free(readData); 545 | 546 | return returnval; 547 | } 548 | 549 | short DisC_REST_DeleteChannel(DisC_session_t *session, DisC_snowflake_t channelId) 550 | { 551 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Deleting channel"); 552 | short returnval; 553 | char *url = NULL; 554 | char *code = NULL; 555 | char *readData = NULL; 556 | unsigned long readDataLen = 0; 557 | 558 | DisC_asprintf(&url, "%s/channels/%s", DISC_REST_APIBASE, channelId); 559 | 560 | DisC_REST_DiscordHTTPRequest(session, &readData, &readDataLen, &code, "DELETE", NULL, url, NULL, 0); 561 | 562 | 563 | //============================================ 564 | 565 | if(strcmp(code, "200") == 0) 566 | { 567 | DisC_AddError(session, DISC_ERROR_NONE); 568 | returnval = DISC_ERROR_NONE; 569 | } 570 | else if(strcmp(code, "401") == 0) 571 | { 572 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "Insufficient permissions for action"); 573 | DisC_AddError(session, DISC_ERROR_PERMISSIONS); 574 | returnval = DISC_ERROR_PERMISSIONS; 575 | } 576 | else{ 577 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "HTTP error: %s: %s", code, readData); 578 | DisC_AddError(session, DISC_ERROR_GENERIC); 579 | returnval = DISC_ERROR_GENERIC; 580 | } 581 | 582 | //free everything================================================== 583 | free(code); 584 | free(url); 585 | free(readData); 586 | 587 | return returnval; 588 | } 589 | 590 | short DisC_REST_GetChannelMessages(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t around, DisC_snowflake_t before, DisC_snowflake_t after, int limit, DisC_message_t **messages, unsigned long *messageNum) 591 | { 592 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Getting channel messages"); 593 | short returnval; 594 | char *queryString = NULL; 595 | char *url = NULL; 596 | char *readData = NULL; 597 | char *code = NULL; 598 | unsigned long readDataLen = 0; 599 | 600 | DisC_message_t *messagesInternal = NULL; 601 | 602 | 603 | DisC_asprintf(&queryString, "?"); 604 | if(around != NULL) 605 | DisC_asprintf(&queryString, "%saround=%s", queryString, around); 606 | 607 | if(before != NULL) 608 | DisC_asprintf(&queryString, "%sbefore=%s", queryString, before); 609 | 610 | if(after != NULL) 611 | DisC_asprintf(&queryString, "%safter=%s", queryString, after); 612 | 613 | if(limit > 0)//from discord docs: " max number of messages to return (1-100)" so 0 should be good for setting to nothing 614 | DisC_asprintf(&queryString, "%s&limit=%d", queryString, limit); 615 | 616 | 617 | //DisC_asprintf(&queryString, "?around=%s&before=%s&after=%s&limit=%d", around, before, after, limit); 618 | DisC_asprintf(&url, "%s/channels/%s/messages%s", DISC_REST_APIBASE, channelId, queryString); 619 | 620 | DisC_REST_DiscordHTTPRequest(session, &readData, &readDataLen, &code, "GET", NULL, url, NULL, 0); 621 | 622 | //json stuff 623 | json_t *retRoot = NULL; 624 | //============================================ 625 | 626 | if(strcmp(code, "200") == 0) 627 | { 628 | 629 | if(readData != NULL) 630 | { 631 | //printf("Parsing JSON\n"); 632 | unsigned long i; 633 | retRoot = json_loads(readData, 0, NULL); 634 | 635 | //printf("JSON: %s\n", json_dumps(retRoot, JSON_INDENT(2))); 636 | //TODO count array elements and allocate a list of messages based on array size 637 | 638 | messagesInternal = calloc(json_array_size(retRoot), sizeof(DisC_message_t)); 639 | if(messagesInternal == NULL) 640 | exit(1); 641 | 642 | for(i = 0; i < json_array_size(retRoot); i++) 643 | { 644 | //printf("%s\n", json_dumps(json_array_get(retRoot, i), 0)); 645 | DisC_message_t *messageInternal = DisC_object_GenerateMessage(session, json_dumps(json_array_get(retRoot, i), 0)); 646 | //messagesInternal[i] = *messageInternal; 647 | //printf("%s\n", messageInternal->content); 648 | //memmove(&messagesInternal[i], messageInternal, sizeof(DisC_message_t)); 649 | messagesInternal[i] = *messageInternal; 650 | //printf("%s\n", messagesInternal[i].content); 651 | free(messageInternal);//TODO actually delete it properly 652 | } 653 | //set stuff 654 | *messageNum = json_array_size(retRoot); 655 | *messages = messagesInternal; 656 | 657 | //printf("last test: %s\n", messages[i]->content); 658 | 659 | DisC_AddError(session, DISC_ERROR_NONE); 660 | returnval = DISC_ERROR_NONE; 661 | } 662 | else 663 | { 664 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_ERROR, "Failed to parse JSON"); 665 | DisC_AddError(session, DISC_ERROR_MEMORY); 666 | returnval = DISC_ERROR_MEMORY; 667 | } 668 | 669 | } 670 | else if(strcmp(code, "401") == 0) 671 | { 672 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "Insufficient permissions for action"); 673 | DisC_AddError(session, DISC_ERROR_PERMISSIONS); 674 | returnval = DISC_ERROR_PERMISSIONS; 675 | } 676 | else{ 677 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "HTTP error: %s: %s", code, readData); 678 | DisC_AddError(session, DISC_ERROR_GENERIC); 679 | returnval = DISC_ERROR_GENERIC; 680 | } 681 | json_decref(retRoot); 682 | free(queryString); 683 | free(code); 684 | free(readData); 685 | free(url); 686 | 687 | return returnval; 688 | } 689 | 690 | short DisC_REST_GetChannelMessage(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId, DisC_message_t **message) 691 | { 692 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Getting channel message"); 693 | short returnval; 694 | char *url = NULL; 695 | char *code = NULL; 696 | char *readData = NULL; 697 | unsigned long readDataLen = 0; 698 | DisC_message_t *messageInternal = NULL; 699 | 700 | DisC_asprintf(&url, "%s/channels/%s/messages/%s", DISC_REST_APIBASE, channelId, messageId); 701 | DisC_REST_DiscordHTTPRequest(session, &readData, &readDataLen, &code, "GET", NULL, url, NULL, 0); 702 | 703 | 704 | //============================================ 705 | 706 | if(strcmp(code, "200") == 0) 707 | { 708 | messageInternal = DisC_object_GenerateMessage(session, readData); 709 | 710 | if(messageInternal != NULL) 711 | { 712 | *message = messageInternal; 713 | 714 | DisC_AddError(session, DISC_ERROR_NONE); 715 | returnval = DISC_ERROR_NONE; 716 | } 717 | else 718 | { 719 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_ERROR, "Failed to parse JSON"); 720 | DisC_AddError(session, DISC_ERROR_MEMORY); 721 | returnval = DISC_ERROR_MEMORY; 722 | } 723 | 724 | } 725 | else if(strcmp(code, "401") == 0) 726 | { 727 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "Insufficient permissions for action"); 728 | DisC_AddError(session, DISC_ERROR_PERMISSIONS); 729 | returnval = DISC_ERROR_PERMISSIONS; 730 | } 731 | else{ 732 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "HTTP error: %s: %s", code, readData); 733 | DisC_AddError(session, DISC_ERROR_GENERIC); 734 | returnval = DISC_ERROR_GENERIC; 735 | } 736 | 737 | 738 | free(code); 739 | free(readData); 740 | free(url); 741 | 742 | return returnval; 743 | } 744 | 745 | short DisC_REST_CreateMessage(DisC_session_t *session, DisC_snowflake_t channelId, char *content, DisC_snowflake_t nonce, DisC_BOOL_t tts, unsigned char *fileData, unsigned long fileDataLen, DisC_embed_t *embed)//get channel id and add parameters 746 | { 747 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Creating message"); 748 | char *url = NULL; 749 | char *readData = NULL; 750 | char *code = NULL; 751 | unsigned long readDataLen = 0; 752 | short returnval; 753 | 754 | json_t *root = json_object(); 755 | 756 | json_object_set_new(root, "content", json_string(content)); 757 | if(nonce != NULL) 758 | { 759 | json_object_set_new(root, "noone", json_string(nonce)); 760 | } 761 | json_object_set_new(root, "tts", json_boolean(tts)); 762 | if(fileData != NULL) 763 | { 764 | json_object_set_new(root, "file", json_integer(fileDataLen)); 765 | } 766 | if(embed != NULL) 767 | { 768 | json_object_set_new(root, "embed", json_string("test"));//TODO add embed parsing from the array of embeds possibly 769 | } 770 | if(fileData != NULL) 771 | { 772 | json_object_set_new(root, "payload_json", json_string("test")); 773 | } 774 | 775 | //=================================================================== 776 | 777 | DisC_asprintf(&url, "%s/channels/%s/messages", DISC_REST_APIBASE, channelId); 778 | if(fileData != NULL) 779 | { 780 | DisC_REST_DiscordHTTPRequest(session, &readData, &readDataLen, &code, "POST", "multipart/form-data", url, json_dumps(root, 0), strlen(json_dumps(root, 0))); 781 | //TODO make it have the parts when sending files 782 | } 783 | else 784 | { 785 | DisC_REST_DiscordHTTPRequest(session, &readData, &readDataLen, &code, "POST", "application/json", url, json_dumps(root, 0), strlen(json_dumps(root, 0))); 786 | } 787 | //============================================ 788 | 789 | if(strcmp(code, "200") == 0) 790 | { 791 | DisC_AddError(session, DISC_ERROR_NONE); 792 | returnval = DISC_ERROR_NONE; 793 | } 794 | else if(strcmp(code, "401") == 0) 795 | { 796 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "Insufficient permissions for action"); 797 | DisC_AddError(session, DISC_ERROR_PERMISSIONS); 798 | returnval = DISC_ERROR_PERMISSIONS; 799 | } 800 | else{ 801 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_WARNING, "HTTP error: %s: %s", code, readData); 802 | DisC_AddError(session, DISC_ERROR_GENERIC); 803 | returnval = DISC_ERROR_GENERIC; 804 | } 805 | 806 | //free everything================================================== 807 | json_decref(root); 808 | free(code); 809 | free(url); 810 | free(readData); 811 | 812 | return returnval; 813 | } 814 | 815 | short DisC_REST_CreateReaction(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId, DisC_emoji_t *emoji) 816 | { 817 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Creating reaction"); 818 | //204 on success 819 | } 820 | 821 | short DisC_REST_DeleteOwnReaction(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId, DisC_emoji_t *emoji) 822 | { 823 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Deleting own reaction"); 824 | //204 on success 825 | } 826 | 827 | short DisC_REST_DeleteUserReaction(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId, DisC_emoji_t *emoji, DisC_snowflake_t userId) 828 | { 829 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Deleting user reaction"); 830 | //204 on success 831 | } 832 | 833 | short DisC_REST_GetReactions(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId, DisC_emoji_t *emoji, DisC_user_t *users) 834 | { 835 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Getting reactions"); 836 | //use GET query string 837 | } 838 | 839 | short DisC_REST_DeleteAllReactions(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId) 840 | { 841 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Deleting all reactions"); 842 | //assumin 204 is good 843 | } 844 | 845 | short DisC_REST_EditMessage(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId) 846 | { 847 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Editing message"); 848 | //basically the same as create message 849 | } 850 | 851 | short DisC_REST_DeleteMessage(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId) 852 | { 853 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Deleting message"); 854 | //204 on success 855 | } 856 | 857 | short DisC_REST_BulkDeleteMessages(DisC_session_t *session, DisC_snowflake_t channelId) 858 | { 859 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Bulk deleting messages"); 860 | //204 on success 861 | } 862 | 863 | short DisC_REST_EditChannelPermissions(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t overwriteId) 864 | { 865 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Editing channel permissions"); 866 | //204 on success 867 | } 868 | 869 | short DisC_REST_GetChannelInvites(DisC_session_t *session, DisC_snowflake_t channelId, DisC_invite_channel_t *invites) 870 | { 871 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Getting channel invites"); 872 | //invites on success 873 | } 874 | 875 | short DisC_REST_CreateChannelInvite(DisC_session_t *session, DisC_snowflake_t channelId, DisC_invite_channel_t *invite) 876 | { 877 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Getting channel invite"); 878 | //returns invite object 879 | } 880 | 881 | short DisC_REST_DeleteChannelPermission(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t overwriteId) 882 | { 883 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Deleting channel permission"); 884 | //204 on success 885 | } 886 | 887 | short DisC_REST_TriggerTypingIndicator(DisC_session_t *session, DisC_snowflake_t channelId) 888 | { 889 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Triggering typing indicator"); 890 | //204 on sucess 891 | } 892 | 893 | short DisC_REST_GetPinnedMessages(DisC_session_t *session, DisC_snowflake_t channelId, DisC_message_t *messages) 894 | { 895 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Getting pinned messages"); 896 | //returns array of message objects 897 | } 898 | 899 | short DisC_REST_AddPinnedChannelMessage(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId) 900 | { 901 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Getting pinned message"); 902 | //204 on success 903 | } 904 | 905 | short DisC_REST_DeletePinnedChannelMessage(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t messageId) 906 | { 907 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Deleting pinned message"); 908 | //204 in success 909 | } 910 | 911 | short DisC_REST_GroupDMAddRecipient(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t userId) 912 | { 913 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Group DM adding recipient"); 914 | //maybe 200 on success 915 | } 916 | 917 | short DisC_REST_GroupDMRemoveRecipient(DisC_session_t *session, DisC_snowflake_t channelId, DisC_snowflake_t userId) 918 | { 919 | DisC_Log(session->logLevel, session->logFileLocation, DISC_SEVERITY_NOTIFY, "Group DM removing recipient"); 920 | //maybe 204 on success 921 | } 922 | 923 | //end of channel REST functions 924 | -------------------------------------------------------------------------------- /src/DisC_util.c: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include 3 | #include 4 | #else 5 | #include 6 | #endif 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "DisC_types.h" 13 | #include "DisC_errors.h" 14 | #include "DisC_util.h" 15 | 16 | short DisC_util_ReadFile(const char *path, unsigned char **data, unsigned long *size) 17 | { 18 | 19 | 20 | 21 | 22 | return DISC_ERROR_NONE; 23 | } 24 | 25 | short DisC_util_WriteFile(const char *path, unsigned char *data, unsigned long *size) 26 | { 27 | 28 | 29 | 30 | 31 | return DISC_ERROR_NONE; 32 | } 33 | 34 | void DisC_Log(short logLevel, const char *logLoc, unsigned short severity, const char *fmt, ...)//TODO just send the session. Having to keep track of the log location and stuff isnt really necessary 35 | { 36 | va_list list; 37 | char *finalString = NULL; 38 | char *tempString = NULL; 39 | char tempBuffer[1]; 40 | int size = 0; 41 | FILE *logFile = NULL; 42 | 43 | va_start(list, fmt); 44 | size = vsnprintf(tempBuffer, 1, fmt, list); 45 | va_end(list); 46 | va_start(list, fmt); 47 | //printf("%d\n", size); 48 | if((tempString = malloc(size + 1)) != NULL) 49 | { 50 | if(vsnprintf(tempString, size + 1, fmt, list) != -1) 51 | { 52 | //printf("%s\n", tempString); 53 | //write to file and print 54 | if(logLoc == NULL)//if file doesnt exist, use the default logfile 55 | { 56 | logLoc = "log.txt"; 57 | } 58 | logFile = fopen(logLoc, "a"); 59 | 60 | if(logFile == NULL)//create a logfile if it doesnt already exist 61 | { 62 | logFile = fopen(logLoc, "w"); 63 | fclose(logFile); 64 | logFile = fopen(logLoc, "a"); 65 | } 66 | //time stuff 67 | time_t t = time(NULL); 68 | struct tm *tm = localtime(&t); 69 | 70 | char* timeStr = asctime(tm); 71 | timeStr[strlen(timeStr)-1] = '\0'; 72 | //time stuff 73 | 74 | //if((finalString = calloc((strlen(timeStr) * sizeof(char)) + (strlen(tempString) * sizeof(char)) + 2), sizeof(char)) == NULL)//2 because the 0x00 and space 75 | if((finalString = calloc(strlen(timeStr) + strlen(tempString) + 2, sizeof(char))) == NULL)//2 because the 0x00 and space 76 | { 77 | exit(1); 78 | } 79 | //*finalString = 0x00; 80 | 81 | strcat(finalString, timeStr); 82 | strcat(finalString, " "); 83 | strcat(finalString, tempString); 84 | 85 | //check severity as well as log level and act accordingly 86 | 87 | switch(logLevel) 88 | { 89 | case 0: 90 | if(severity == 2) 91 | fprintf(logFile, "%s\n", finalString); 92 | break; 93 | case 1: 94 | fprintf(logFile, "%s\n", finalString); 95 | break; 96 | case 2: 97 | fprintf(logFile, "%s\n", finalString); 98 | fprintf(stdout, "%s\n", finalString); 99 | break; 100 | } 101 | 102 | fclose(logFile); 103 | free(tempString); 104 | free(finalString); 105 | //free(timeStr); 106 | } 107 | } 108 | else 109 | { 110 | exit(1); 111 | } 112 | 113 | va_end(list); 114 | 115 | } 116 | 117 | char *DisC_strmkdup(const char *str) 118 | { 119 | if(str != NULL) 120 | { 121 | const char *returnStr = malloc((strlen(str) + 1) * sizeof(const char)); 122 | if(returnStr != NULL) 123 | { 124 | strcpy(returnStr, str); 125 | } 126 | return (char *)returnStr; 127 | } 128 | else 129 | { 130 | return NULL; 131 | } 132 | } 133 | //TODO if I need to duplicate intergers too 134 | 135 | short DisC_asprintf(char **string, const char *fmt, ...) 136 | { 137 | va_list list; 138 | char *tempString = NULL; 139 | char *oldstring = NULL; 140 | int size = 0; 141 | 142 | if(*string != NULL) 143 | { 144 | //free(*string); 145 | oldstring = *string; 146 | } 147 | 148 | va_start(list, fmt); 149 | size = vsnprintf(tempString, 0, fmt, list); 150 | va_end(list); 151 | va_start(list, fmt); 152 | 153 | if((tempString = malloc(size + 1)) != NULL) 154 | { 155 | if(vsnprintf(tempString, size + 1, fmt, list) != -1) 156 | { 157 | *string = tempString; 158 | if(oldstring != NULL) 159 | { 160 | free(oldstring); 161 | } 162 | return size; 163 | } 164 | else 165 | { 166 | *string = NULL; 167 | if(oldstring != NULL) 168 | { 169 | free(oldstring); 170 | } 171 | return -1; 172 | } 173 | } 174 | va_end(list); 175 | } 176 | 177 | void DisC_Delay(unsigned long milisec) 178 | { 179 | #ifdef _WIN32 180 | //do the windows version 181 | Sleep(milisec);//TODO do this right 182 | #else 183 | long sec = milisec / 1000; 184 | struct timespec req; 185 | 186 | if(milisec >= 1000) 187 | { 188 | req.tv_sec = sec;//TODO un linux-only this. iirc BSD does something differrent 189 | req.tv_nsec = (milisec * 1000000) - (sec * 1000);//to convert it to nanoseconds 190 | } 191 | else 192 | { 193 | req.tv_sec = 0;//TODO un linux-only this. iirc BSD does something differrent 194 | req.tv_nsec = (milisec * 1000000);//to convert it to nanoseconds 195 | } 196 | nanosleep(&req, NULL); 197 | #endif 198 | } 199 | 200 | u_int64_t DisC_GetTick() 201 | { 202 | u_int64_t tick; 203 | 204 | #ifdef _WIN32 205 | //do windows time stuff 206 | #else//in the future, I need to have a way of deciding between gettimeofday and clock_gettime because some things dont support one or the other. 207 | struct timeval tickTimeval; 208 | gettimeofday(&tickTimeval, NULL); 209 | //tick = (tickTimeval.tv_sec * 1000000) + (tickTimeval.tv_usec); 210 | tick = tickTimeval.tv_sec;//temporary solution 211 | #endif 212 | 213 | return tick; 214 | } 215 | 216 | 217 | //stupid thing copied from stackoverflow. Im only using it to check my websocket stuff. Will delete 218 | void print_bits(unsigned char octet) 219 | { 220 | int z = 128, oct = octet; 221 | 222 | while (z > 0) 223 | { 224 | if (oct & z) 225 | printf("1"); 226 | else 227 | printf("0"); 228 | z >>= 1; 229 | } 230 | printf(" "); 231 | } 232 | 233 | //cleaners====================================================================== 234 | 235 | short Disc_util_FreeChannel(DisC_session_t *session, DisC_channel_t *channel) 236 | { 237 | 238 | DisC_AddError(session, DISC_ERROR_NONE); 239 | return DISC_ERROR_NONE; 240 | } 241 | --------------------------------------------------------------------------------