discordc

discordc - C Discord ping bot.
git clone git://git.beep.wimdupont.com/discordc.git
Log | Files | Refs | README | LICENSE

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 }