discordc.c (4216B)
1 #include <stdbool.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <time.h> 6 #include <unistd.h> 7 8 #include "config.h" 9 #include "discordapi.h" 10 #include "discordjson.h" 11 #include "timeutil.h" 12 13 #define EVENT_MAX 262144 14 #define CLOSE_REASON_MAX 256 15 16 static void run_bot(void); 17 static void handle_gateway_payload(DiscordGateway *gateway, 18 const char *payload, long *seq, long *heartbeat_ms, 19 struct timespec *next_heartbeat, char *lastid); 20 static void send_identify(DiscordGateway *gateway); 21 static void send_heartbeat(DiscordGateway *gateway, const long seq); 22 static void send_pong(const long latency); 23 static char * cpstr(char *dest, const char *src); 24 25 int 26 main(void) 27 { 28 if (discord_bot_token[0] == '\0') { 29 fprintf(stderr, "Empty discord_bot_token in config.h\n"); 30 return EXIT_FAILURE; 31 } 32 if (discord_channel_id[0] == '\0') { 33 fprintf(stderr, "Empty discord_channel_id in config.h\n"); 34 return EXIT_FAILURE; 35 } 36 37 discordapi_init(); 38 run_bot(); 39 discordapi_cleanup(); 40 41 return EXIT_SUCCESS; 42 } 43 44 void 45 run_bot(void) 46 { 47 char payload[EVENT_MAX], close_reason[CLOSE_REASON_MAX], lastid[ID_MAX] = ""; 48 unsigned int close_code; 49 long seq = -1, heartbeat_ms = 0; 50 struct timespec next_heartbeat = {0}; 51 DiscordGateway gateway; 52 DiscordApiRecvStatus status; 53 54 if (discordapi_gateway_open(&gateway, discord_gateway_url, 55 discord_user_agent) != 0) { 56 fprintf(stderr, "Failed to connect Discord Gateway\n"); 57 return; 58 } 59 60 for (;;) { 61 if (heartbeat_ms > 0 && heartbeat_due(&next_heartbeat)) { 62 send_heartbeat(&gateway, seq); 63 schedule_heartbeat(&next_heartbeat, heartbeat_ms); 64 } 65 66 status = discordapi_gateway_recv(&gateway, payload, sizeof(payload), 67 &close_code, close_reason, sizeof(close_reason)); 68 if (status == DISCORDAPI_RECV_NONE) { 69 usleep(50000); 70 continue; 71 } 72 if (status == DISCORDAPI_RECV_ERROR) 73 break; 74 if (status == DISCORDAPI_RECV_CLOSED) { 75 fprintf(stderr, "Gateway closed connection: %u %s\n", 76 close_code, close_reason); 77 if (close_code == 4014) 78 fprintf(stderr, "Enable Message Content Intent in " 79 "Discord Developer Portal -> Bot\n"); 80 break; 81 } 82 83 handle_gateway_payload(&gateway, payload, &seq, &heartbeat_ms, 84 &next_heartbeat, lastid); 85 } 86 87 discordapi_gateway_close(&gateway); 88 } 89 90 void 91 handle_gateway_payload(DiscordGateway *gateway, const char *payload, 92 long *seq, long *heartbeat_ms, 93 struct timespec *next_heartbeat, char *lastid) 94 { 95 DiscordEvent event; 96 97 if (!parse_gateway_json(payload, &event)) 98 return; 99 if (event.seq >= 0) 100 *seq = event.seq; 101 if (event.heartbeat_ms > 0) { 102 *heartbeat_ms = event.heartbeat_ms; 103 send_identify(gateway); 104 send_heartbeat(gateway, *seq); 105 schedule_heartbeat(next_heartbeat, *heartbeat_ms); 106 } 107 if (event.heartbeat_requested) { 108 send_heartbeat(gateway, *seq); 109 schedule_heartbeat(next_heartbeat, *heartbeat_ms); 110 } 111 if (event.has_message 112 && strcmp(event.channel_id, discord_channel_id) == 0 113 && strcmp(event.content, "ping") == 0 114 && strcmp(event.message_id, lastid) != 0) { 115 cpstr(lastid, event.message_id); 116 send_pong(parse_latency(event.timestamp)); 117 } 118 } 119 120 void 121 send_identify(DiscordGateway *gateway) 122 { 123 char *json_string = build_identify_json(discord_bot_token, 124 discord_intents); 125 126 if (json_string == NULL) { 127 fprintf(stderr, "Failed to build identify payload\n"); 128 return; 129 } 130 131 discordapi_gateway_send(gateway, json_string); 132 free(json_string); 133 } 134 135 void 136 send_heartbeat(DiscordGateway *gateway, const long seq) 137 { 138 char *json_string = build_heartbeat_json(seq); 139 140 if (json_string == NULL) { 141 fprintf(stderr, "Failed to build heartbeat payload\n"); 142 return; 143 } 144 145 discordapi_gateway_send(gateway, json_string); 146 free(json_string); 147 } 148 149 void 150 send_pong(const long latency) 151 { 152 char pong[64]; 153 char *json_string; 154 155 snprintf(pong, sizeof(pong), "pong %ld ms", latency); 156 if ((json_string = build_message_json(pong)) == NULL) { 157 fprintf(stderr, "Failed to build message payload\n"); 158 return; 159 } 160 161 discordapi_post_message(discord_api_base, discord_channel_id, 162 discord_bot_token, discord_user_agent, json_string); 163 free(json_string); 164 } 165 166 char * 167 cpstr(char *dest, const char *src) 168 { 169 size_t cplen = strlen(src); 170 171 memcpy(dest, src, cplen); 172 dest[cplen] = '\0'; 173 174 return dest; 175 }