summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README.md18
-rw-r--r--src/api.c574
-rw-r--r--src/i18n.h5
-rw-r--r--src/main.c2
5 files changed, 597 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index 09aefe5..253209f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
default:
- gcc -g -Ilib -Isrc -I. src/main.c -lcurl -odiscord.c
+ gcc -g -Ilib -Isrc -I. src/api.c -lcurl -odiscord.c
prepare:
wget https://raw.githubusercontent.com/DaveGamble/cJSON/master/cJSON.c -O lib/cJSON.c
wget https://raw.githubusercontent.com/DaveGamble/cJSON/master/cJSON.h -O lib/cJSON.h
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f6c9ba6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# discord.c
+
+an alternative client for the discord messaging platform, written in the C programming language.
+
+## requirements
+
+* a POSIX system
+* GNU C library
+* GNU compiler collection
+* GNU Make
+* libcurl 7.17.0 or newer with HTTPS support
+
+## instructions
+
+```
+make
+./discord.c -e email@address.example -p password
+```
diff --git a/src/api.c b/src/api.c
new file mode 100644
index 0000000..adcdf3c
--- /dev/null
+++ b/src/api.c
@@ -0,0 +1,574 @@
+#pragma once
+#define _XOPEN_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <curl/curl.h>
+#include <unistd.h>
+#include <i18n.h>
+#include <string.h>
+#include <lib.c>
+#include <cJSON.h>
+#include <cJSON.c>
+#include <time.h>
+#include <stdarg.h>
+#include <printf.h>
+#define DC_API_PREFIX "https://discord.com/api/v8/" /* this can be a format string, DO NOT use format characters inside */
+#define DC_LOGIN_FORMAT "{\"login\":\"%s\",\"password\":\"%s\",\"undelete\":false,\"captcha_key\":null,\"login_source\":null,\"gift_code_sku_id\":null}"
+#define DC_USER_AGENT "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
+#define DC_ERROR(e, s, m, ...) dc_push_error(e, s, __func__, __FILE__, __LINE__, 0##__VA_OPT__(1), m __VA_OPT__(,) __VA_ARGS__)
+#define DC_CLIENT_ERROR(c, m, ...) DC_ERROR(c->errors, &c->errors_sizeof, m __VA_OPT__(,) __VA_ARGS__) /* yeah, that m is not a typo */
+#define DC_API(curl, body, endpoint, ...) dc_api(curl, body, 0##__VA_OPT__(1), endpoint __VA_OPT__(,) __VA_ARGS__)
+#define cJSON_GetObjectItem2(root, name1, name2) (cJSON_GetObjectItem(root, name1) ? cJSON_GetObjectItem(cJSON_GetObjectItem(root, name1), name2) : NULL)
+#define DC_TIMESTAMP_FORMAT "%Y-%m-%dT%H:%M:%S.XXXXXX%z"
+struct dc_error {
+ size_t line;
+ const char * function /* this is a static string, so it is not copied, do not free it */;
+ char * file /* this is a static string, so it is not copied, do not free it */;
+ char * message /* this is copied, caller can free it if it was dynamically created by it, it must be freed before destroying struct */;
+ time_t time;
+ short unsigned int reported /* 0: error was not yet shown to the user, 1: error was already shown to the user */;
+};
+int dc_error_free(struct dc_error * e) {
+ free(e->message); e->message = NULL; /* other strings are static */
+ e->line = 0;
+ e->function = NULL;
+ e->file = NULL;
+ e->reported = 0;
+ free(e);
+};
+struct dc_message {
+ char * username;
+ int discriminator;
+ char * content;
+ time_t time;
+ struct dc_channel * channel;
+ unsigned short int tts;
+ unsigned long long int id;
+};
+int dc_message_free (struct dc_message * m) {
+ free(m->username); m->username = NULL;
+ free(m->content); m->content = NULL;
+ m->channel = NULL;
+ m->tts = 0;
+ m->discriminator = -1;
+ m->id = 0;
+ /* i'm not sure how to "unset" time_t ... */
+ free(m);
+}
+int dc_message_compare (const void * a, const void * b) {
+ struct dc_message * c = (struct dc_message *) a;
+ struct dc_message * d = (struct dc_message *) b;
+ /* strežnik v arrayu najprej pošlje sporočila iz sedanjosti, nato tista iz preteklosti, zato bo tudi sortiran array takšen */
+ return c->time > d->time ? -1 : c->time < d->time ? 1 : 0;
+}
+struct dc_channel {
+ char * name;
+ char * topic;
+ unsigned long long int id;
+ struct dc_guild * guild;
+ struct dc_message * messages;
+ size_t messages_sizeof;
+ unsigned short int sent /* describes status, was message already sent (1) or is it to-be sent (0)? */;
+};
+int dc_channel_free (struct dc_channel * ch) {
+ free(ch->name); ch->name = NULL;
+ free(ch->topic); ch->topic = NULL;
+ ch->guild = NULL;
+ ch->id = 0;
+ for (int i = 0; i < ch->messages_sizeof; i++)
+ dc_message_free(&ch->messages[i]);
+ ch->messages_sizeof = 0;
+ free(ch);
+}
+struct dc_guild {
+ char * name;
+ unsigned long long id;
+ size_t channels_sizeof;
+ struct dc_channel * channels;
+};
+int dc_guild_free (struct dc_guild * g) {
+ free(g->name); g->name = NULL;
+ g->id = 0;
+ for (int i = 0; i < g->channels_sizeof; i++)
+ dc_channel_free(&g->channels[i]);
+ g->channels_sizeof = 0;
+ free(g);
+}
+struct dc_client {
+ CURL * curl;
+ struct curl_slist * curl_headers;
+ char * authorization;
+ char * email; /* email and password are copied, even though they are sometimes static ENV vars - they are not always! */
+ char * password;
+ char * username;
+ int discriminator;
+ struct dc_guild * guilds;
+ size_t guilds_sizeof;
+ struct dc_channel * joinedchannel;
+ struct dc_error * errors;
+ size_t errors_sizeof;
+ struct dc_message * sent_messages;
+ size_t sent_messages_sizeof;
+};
+int dc_client_free (struct dc_client * c) {
+ curl_easy_cleanup(c->curl);
+ curl_slist_free_all(c->curl_headers);
+ free(c->authorization); c->authorization = NULL;
+ free(c->email); c->email = NULL;
+ free(c->password); c->password = NULL;
+ free(c->username); c->username = NULL;
+ c->discriminator = -1;
+ c->joinedchannel = NULL;
+ for (int i = 0; i < c->guilds_sizeof; i++)
+ dc_guild_free(&c->guilds[i]);
+ c->guilds_sizeof = 0;
+ for (int i = 0; i < c->errors_sizeof; i++)
+ dc_error_free(&c->errors[i]);
+ c->errors_sizeof = 0;
+ for (int i = 0; i < c->sent_messages_sizeof; c++)
+ dc_message_free(&c->sent_messages[i]);
+ c->sent_messages_sizeof = 0;
+ free(c);
+}
+int dc_push_error (struct dc_error * e, size_t * s, const char * c, char * f, size_t l, unsigned short int isfmt, char * m, ...) {
+ e = realloc(e, sizeof(struct dc_error)*++*s); /* note: format arguments are evaluated twice */
+ size_t strlenm = strlen(m);
+ size_t va_count = parse_printf_format(m, 0, NULL);
+ if (isfmt && va_count > 0) {
+ va_list ap, ap2;
+ va_start(ap, m);
+ va_copy(ap2, ap);
+ strlenm = vsnprintf(NULL, 0, m, ap);
+ e[*s-1].message = malloc(sizeof(char)*strlenm+1);
+ vsnprintf(e[*s-1].message, strlenm+1, m, ap2);
+ va_end(ap);
+ va_end(ap2);
+ } else {
+ e[*s-1].message = malloc(sizeof(char)*strlenm+1);
+ strcpy(e[*s-1].message, m);
+ }
+ e[*s-1].file = f;
+ e[*s-1].line = l;
+ e[*s-1].function = c /* Caller */;
+ e[*s-1].time = time(NULL);
+ e[*s-1].reported = 0;
+ return 1;
+}
+cJSON * dc_api (CURL * curl, char * body, int isfmt, char * endpoint, ...) { /* note: format arguments are evaluated twice */
+ if (!curl)
+ return NULL;
+ if (!endpoint)
+ return NULL;
+ cJSON * json = NULL;
+ struct writefunc_string s;
+ init_writefunc_string(&s);
+ size_t va_count = parse_printf_format(endpoint, 0, NULL);
+ char * endpoint_formatted = NULL;
+ if (isfmt && va_count > 0) {
+ va_list ap, ap2;
+ va_start(ap, endpoint);
+ va_copy(ap2, ap);
+ size_t strlenm = vsnprintf(NULL, 0, endpoint, ap);
+ endpoint_formatted = malloc(sizeof(char)*strlenm+1);
+ vsnprintf(endpoint_formatted, strlenm, endpoint_formatted, ap2); /* sn instead of s because double evaulation may produce */
+ va_end(ap); /* larger output the next time and lead to overflow */
+ va_end(ap2);
+ }
+ curl_easy_setopt(curl, CURLOPT_URL, endpoint_formatted ? endpoint_formatted : endpoint);
+ if (!body)
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
+ else
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
+ if (curl_easy_perform(curl) != CURLE_OK)
+ goto rc;
+ json = cJSON_Parse(s.ptr);
+ rc:
+ free(endpoint_formatted);
+ free(s.ptr); s.ptr = NULL;
+ return json;
+}
+int dc_login (struct dc_client * c) {
+ int rs = 1;
+ struct writefunc_string s;
+ char * data = NULL;
+ cJSON * json = NULL;
+ if (!c)
+ return -1;
+ if (!c->email || !c->password) {
+ DC_CLIENT_ERROR(c, DC_I18N_MISSING_EP);
+ return -2;
+ }
+ if (!c->curl)
+ c->curl = curl_easy_init();
+ if (!c->curl) {
+ DC_CLIENT_ERROR(c, "curl_easy_init() " DC_I18N_FAILED);
+ return -3;
+ }
+ init_writefunc_string(&s);
+ data = malloc(strlen(DC_LOGIN_FORMAT)+strlen(c->email)+strlen(c->password)+1);
+ CURLcode res;
+ sprintf(data, DC_LOGIN_FORMAT, c->email, c->password);
+ c->curl_headers = curl_slist_append(c->curl_headers, "Content-Type: application/json");
+ c->curl_headers = curl_slist_append(c->curl_headers, "User-Agent: " DC_USER_AGENT);
+ curl_easy_setopt(c->curl, CURLOPT_URL, DC_API_PREFIX "auth/login");
+ curl_easy_setopt(c->curl, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(c->curl, CURLOPT_POSTFIELDS, data);
+ curl_easy_setopt(c->curl, CURLOPT_WRITEFUNCTION, writefunc);
+ curl_easy_setopt(c->curl, CURLOPT_HTTPHEADER, c->curl_headers);
+ curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s);
+ res = curl_easy_perform(c->curl);
+ if (res != CURLE_OK) {
+ DC_CLIENT_ERROR(c, "curl_easy_perform() " DC_I18N_FAILED ": %s", curl_easy_strerror(res)); /* yeah, format strings are supported */
+ rs = -4;
+ goto rc;
+ }
+ json = cJSON_Parse(s.ptr);
+ if (!json) {
+ const char *error_ptr = cJSON_GetErrorPtr();
+ if (!error_ptr) {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr);
+ } else {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
+ }
+ rs = -5;
+ goto rc;
+ }
+ cJSON * token = cJSON_GetObjectItem(json, "token");
+ if (!cJSON_IsString(token)) {
+ DC_CLIENT_ERROR(c, "!cJSON_IsString(token)");
+ rs = -6;
+ goto rc;
+ }
+ c->authorization = realloc(c->authorization, strlen(token->valuestring)+1);
+ strcpy(c->authorization, token->valuestring);
+ data = realloc(data, strlen(c->authorization)+strlen("Authorization: ")+1);
+ strcpy(data, "Authorization: ");
+ strcat(data, c->authorization);
+ free(s.ptr); s.ptr = NULL;
+ init_writefunc_string(&s);
+ curl_easy_setopt(c->curl, CURLOPT_URL, DC_API_PREFIX "users/@me");
+ curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L);
+ curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s);
+ c->curl_headers = curl_slist_append(c->curl_headers, data);
+ res = curl_easy_perform(c->curl);
+ if (res != CURLE_OK) {
+ DC_CLIENT_ERROR(c, "curl_easy_perform() " DC_I18N_FAILED ": %s", curl_easy_strerror(res));
+ rs = -7;
+ goto rc;
+ }
+ cJSON_Delete(json);
+ json = cJSON_Parse(s.ptr);
+ if (!json) {
+ const char *error_ptr = cJSON_GetErrorPtr();
+ if (!error_ptr) {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr);
+ } else {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
+ }
+ rs = -8;
+ goto rc;
+ }
+ token = cJSON_GetObjectItem(json, "username");
+ cJSON * token2 = cJSON_GetObjectItem(json, "discriminator");
+ if (!cJSON_IsString(token) || !cJSON_IsString(token2)) {
+ DC_CLIENT_ERROR(c, "!cJSON_IsString(token) || !cJSON_IsString(token2)");
+ rs = -9;
+ goto rc;
+ }
+ c->username = realloc(c->username, strlen(token->valuestring)+1);
+ strcpy(c->username, token->valuestring);
+ c->discriminator = strtol(token2->valuestring, NULL, 10);
+ rc:
+ free(s.ptr); s.ptr = NULL;
+ free(data); data = NULL;
+ cJSON_Delete(json);
+ return rs;
+}
+int dc_fetch_guilds (struct dc_client * c) {
+ if (!c)
+ return -5;
+ int rs = 1;
+ struct writefunc_string s;
+ char * value = NULL;
+ char * value2 = NULL;
+ CURLcode res;
+ cJSON * json = NULL;
+ if (!c->username || !c->username[0])
+ if ((rs = dc_login(c)) < 0) {
+ DC_CLIENT_ERROR(c, "dc_login(c) " DC_I18N_FAILED " (%d)", rs);
+ return -1;
+ } else rs = 1;
+ curl_easy_setopt(c->curl, CURLOPT_URL, DC_API_PREFIX "users/@me/guilds");
+ curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L);
+ curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s);
+ init_writefunc_string(&s);
+ res = curl_easy_perform(c->curl);
+ if (res != CURLE_OK) {
+ DC_CLIENT_ERROR(c, "curl_easy_perform(c->curl) " DC_I18N_FAILED ": %s", curl_easy_strerror(res));
+ rs = -2;
+ goto rc;
+ }
+ json = cJSON_Parse(s.ptr);
+ if (!json) {
+ const char *error_ptr = cJSON_GetErrorPtr();
+ if (!error_ptr) {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr);
+ } else {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
+ }
+ rs = -3;
+ goto rc;
+ }
+ if (!cJSON_IsArray(json)) {
+ DC_CLIENT_ERROR(c, "!cJSON_IsArray(json)");
+ rs = -4;
+ goto rc;
+ }
+ for (int i = 0; i < c->guilds_sizeof; i++)
+ dc_guild_free(&c->guilds[i]);
+ c->guilds = NULL;
+ c->guilds_sizeof = 0;
+ cJSON * guild = NULL;
+ cJSON_ArrayForEach(guild, json) {
+ value = cJSON_GetStringValue(cJSON_GetObjectItem(json, "id"));
+ value2= cJSON_GetStringValue(cJSON_GetObjectItem(json, "name"));
+ if (!value || !value2) {
+ DC_CLIENT_ERROR(c, "!cJSON_GetStringValue(cJSON_GetObjectItem(json, \"id\" || \"name\"))");
+ continue;
+ }
+ c->guilds = realloc(c->guilds, ++c->guilds_sizeof);
+ c->guilds[c->guilds_sizeof-1].name = malloc(strlen(value)+1);
+ strcpy(c->guilds[c->guilds_sizeof-1].name, value);
+ c->guilds[c->guilds_sizeof-1].id = strtoull(value2, NULL, 10);
+ c->guilds[c->guilds_sizeof-1].channels_sizeof = 0;
+ c->guilds[c->guilds_sizeof-1].channels = NULL;
+ }
+ rc:
+ free(s.ptr); s.ptr = NULL;
+ cJSON_Delete(json); json = NULL;
+ return rs;
+}
+int dc_fetch_channels (struct dc_client * c, struct dc_guild * g) {
+ int rs = 1;
+ if (!c)
+ return -1;
+ if (!c->username || !c->username[0])
+ if ((rs = dc_login(c)) < 0) {
+ DC_CLIENT_ERROR(c, "dc_login(c) " DC_I18N_FAILED " (%d)", rs);
+ return -2;
+ } else rs = 1;
+ if (!g || !g->id) {
+ DC_CLIENT_ERROR(c, "!g || !g->id");
+ return -3;
+ }
+ CURLcode res;
+ struct writefunc_string s;
+ init_writefunc_string(&s);
+ cJSON * json = NULL;
+ char * url = malloc(strlen(DC_API_PREFIX "guilds/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/channels")+1);
+ sprintf(url, DC_API_PREFIX "guilds/%llu/channels", g->id);
+ curl_easy_setopt(c->curl, CURLOPT_URL, url);
+ curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L);
+ curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s);
+ res = curl_easy_perform(c->curl);
+ if (res != CURLE_OK) {
+ DC_CLIENT_ERROR(c, "curl_easy_perform(c->curl) " DC_I18N_FAILED ": %s", curl_easy_strerror(res));
+ rs = -4;
+ goto rc;
+ }
+ json = cJSON_Parse(s.ptr);
+ if (!json) {
+ const char *error_ptr = cJSON_GetErrorPtr();
+ if (!error_ptr) {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr);
+ } else {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
+ }
+ rs = -5;
+ goto rc;
+ }
+ if (!cJSON_IsArray(json)) {
+ DC_CLIENT_ERROR(c, "!cJSON_IsArray(json), s.ptr = %s", s.ptr);
+ rs = -6;
+ goto rc;
+ }
+ cJSON * channel = NULL;
+ for (int i = 0; i < g->channels_sizeof; i++)
+ dc_channel_free(&g->channels[i]);
+ g->channels_sizeof = 0;
+ cJSON_ArrayForEach(channel, json) {
+ char * topic = cJSON_GetStringValue(cJSON_GetObjectItem(json, "topic"));
+ char * name = cJSON_GetStringValue(cJSON_GetObjectItem(json, "name"));
+ char * id = cJSON_GetStringValue(cJSON_GetObjectItem(json, "id"));
+ double type = cJSON_GetNumberValue(cJSON_GetObjectItem(json, "type"));
+ if (!id || !name || type == NAN) {
+ DC_CLIENT_ERROR(c, "!id || !name || type == NAN");
+ continue;
+ }
+ if (type != 0) /* if it's not a text channel (z. B. voice channel, category, ...) */
+ continue;
+ if (!topic)
+ topic = "";
+ g->channels = realloc(g->channels, ++g->channels_sizeof);
+ g->channels[g->channels_sizeof-1].name = malloc(strlen(name)+1);
+ strcpy(g->channels[g->channels_sizeof-1].name, name);
+ g->channels[g->channels_sizeof-1].topic = malloc(strlen(topic)+1);
+ strcpy(g->channels[g->channels_sizeof-1].topic, topic);
+ g->channels[g->channels_sizeof-1].id = strtoull(id, NULL, 10);
+ g->channels[g->channels_sizeof-1].guild = g;
+ g->channels[g->channels_sizeof-1].messages = NULL;
+ g->channels[g->channels_sizeof-1].messages_sizeof = 0;
+ }
+ rc:
+ free(s.ptr); s.ptr = NULL;
+ free(url); url = NULL;
+ cJSON_Delete(json); json = NULL;
+ return rs;
+}
+int dc_send_message (struct dc_client * c, struct dc_message * m) {
+ int rs = 1;
+ struct tm tm; /* not used at all, you can delete it, I am autistic xD */
+ if (!c)
+ return -1;
+ if (!c->username || !c->username[0])
+ if ((rs = dc_login(c)) < 0) {
+ DC_CLIENT_ERROR(c, "dc_login(c) " DC_I18N_FAILED " (%d)", rs);
+ return -2;
+ } else rs = 1;
+ /* it'd be bad to assume joinedchannel if the following is false though */
+ if (!m) {
+ DC_CLIENT_ERROR(c, "!m");
+ return -3;
+ }
+ cJSON * json = cJSON_CreateObject();
+ cJSON * nons = cJSON_CreateNumber(rand());
+ cJSON_AddItemToObject(json, "nonce", nons);
+ cJSON * content = cJSON_CreateString(m->content);
+ cJSON_AddItemToObject(json, "content", content);
+ cJSON * tts = m->tts ? cJSON_CreateTrue() : cJSON_CreateFalse();
+ cJSON_AddItemToObject(json, "tts", tts);
+ char * body = cJSON_Print(json);
+ if (!body) {
+ DC_CLIENT_ERROR(c, "cJSON_Print " DC_I18N_FAILED);
+ rs = -4;
+ goto rc;
+ }
+ cJSON_Delete(json); json = NULL;
+ /* {content: "yeet", nonce: "820762917392613376", tts: false} */
+ json = DC_API(c->curl, body, DC_API_PREFIX "channels/%llu/messages", m->channel->id);
+ if (!json) {
+ const char *error_ptr = cJSON_GetErrorPtr();
+ if (!error_ptr) {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr);
+ } else {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
+ }
+ rs = -5;
+ goto rc;
+ }
+ char * discriminator = cJSON_GetStringValue(cJSON_GetObjectItem2(json, "author", "discriminator"));
+ if (!discriminator) {
+ DC_CLIENT_ERROR(c, "!discriminator");
+ rs = -6;
+ goto rc;
+ }
+ m->discriminator = strtol(discriminator, NULL, 10);
+ char * username = cJSON_GetStringValue(cJSON_GetObjectItem2(json, "author", "username"));
+ if (!username) {
+ DC_CLIENT_ERROR(c, "!username");
+ rs = -6;
+ goto rc;
+ }
+ m->username = malloc(strlen(username)+1);
+ strcpy(m->username, username); /* we don't directly point as that changes when we delete */
+ rc:
+ free(body); body = NULL;
+ cJSON_Delete(json); json = NULL;
+ return rs;
+}
+int dc_fetch_messages (struct dc_client * c, struct dc_channel * ch) {
+ int rs = 1;
+ struct tm tm;
+ if (!c)
+ return -1;
+ if (!c->username || !c->username[0])
+ if ((rs = dc_login(c)) < 0) {
+ DC_CLIENT_ERROR(c, "dc_login(c) " DC_I18N_FAILED " (%d)", rs);
+ return -2;
+ } else rs = 1;
+ /* it'd be bad to assume joinedchannel if the following is false though */
+ if (!ch || !ch->id) {
+ DC_CLIENT_ERROR(c, "!ch || !ch->id");
+ return -3;
+ }
+ cJSON * json = DC_API(c->curl, NULL, DC_API_PREFIX "channels/%llu/messages?limit=100", ch->id);
+ if (!json) {
+ const char *error_ptr = cJSON_GetErrorPtr();
+ if (!error_ptr) {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr);
+ } else {
+ DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED);
+ }
+ rs = -5;
+ goto rc;
+ }
+ if (!cJSON_IsArray(json)) {
+ DC_CLIENT_ERROR(c, "!cJSON_IsArray(json)");
+ rs = -6;
+ goto rc;
+ }
+ cJSON * message = NULL;
+ /*
+ for (int i = 0; i < ch->messages_sizeof; i++)
+ dc_message_free(&ch->messages[i]);
+ ch->messages_sizeof = 0;
+ */ /* we'll rather add messages to existing ones */
+ cJSON_ArrayForEach(message, json) {
+ char * timestamp = cJSON_GetStringValue(cJSON_GetObjectItem(json, "timestamp"));
+ char * content = cJSON_GetStringValue(cJSON_GetObjectItem(json, "content"));
+ char * id = cJSON_GetStringValue(cJSON_GetObjectItem(json, "id"));
+ char * discriminator = cJSON_GetStringValue(cJSON_GetObjectItem2(json, "author", "discriminator"));
+ char * username = cJSON_GetStringValue(cJSON_GetObjectItem2(json, "author", "username"));
+ if (!id || !timestamp || !content || !username || !discriminator) {
+ DC_CLIENT_ERROR(c, "!id || !timestamp || !content || !username || discriminator < 0");
+ continue;
+ }
+ if (strlen(timestamp) < 26) {
+ DC_CLIENT_ERROR(c, "strlen(timestamp) < 26");
+ continue;
+ }
+ for (int i = 20; i <= 25; i++)
+ timestamp[i] == 'X'; /* because strptime does not have wildcard support and those numbers are sub-second fractions */
+ if (!strptime(timestamp, DC_TIMESTAMP_FORMAT, &tm)) {
+ DC_CLIENT_ERROR(c, "strptime(timestamp, DC_TIMESTAMP_FORMAT, &tm) " DC_I18N_FAILED);
+ continue;
+ }
+ unsigned long long int idull = strtoull(id, NULL, 10);
+ for (int i = 0; i < ch->messages_sizeof; i++)
+ if (idull == ch->messages[i].id)
+ continue; /* remove duplicates */
+ ch->messages = realloc(ch->messages, ++ch->messages_sizeof);
+#define DC_FMTM /* fetch messages this message */ ch->messages[ch->messages_sizeof-1]
+ DC_FMTM.time = mktime(&tm);
+ DC_FMTM.content = malloc(strlen(content)+1);
+ strcpy(DC_FMTM.content, content);
+ DC_FMTM.username = malloc(strlen(username)+1);
+ strcpy(DC_FMTM.username, username);
+ DC_FMTM.id = idull;
+ DC_FMTM.discriminator = strtol(discriminator, NULL, 10);
+ DC_FMTM.channel = ch;
+ }
+ qsort(ch->messages, ch->messages_sizeof, sizeof(struct dc_message), dc_message_compare); /* we sort so that present messages are in the start of the array and old messages are to the end of the array */
+ rc:
+ cJSON_Delete(json); json = NULL;
+ return rs;
+}
+struct dc_api_thread_control {
+ unsigned short int power; /* 1 if the thread should run, set it to 0 for the thread to return at the end of the loop */
+ struct dc_client * clients; /* "array" of clients the thread should manage, ONLY ONE dc_api_thread PER PROCESS! */
+ size_t dc_clients_sizeof;
+};
+int dc_api_thread (struct dc_api_thread_control * t) { /* updates messages and sends messages when they are in the outbox */
+ return -1;
+} /* the thread shall use mutexes when doing things with shared memory - client structs */
diff --git a/src/i18n.h b/src/i18n.h
index 6439daa..2a982d7 100644
--- a/src/i18n.h
+++ b/src/i18n.h
@@ -1,11 +1,12 @@
-#define DC_I18N_FAILED "neuspel"
+#define DC_I18N_FAILED "neuspel" /**/
#define DC_I18N_UNREC_ARG "neprepoznan argument %c, poskusi -h"
#define DC_I18N_USAGE "uporaba: %s -e naslov@example -p geslo"
#define DC_I18N_ARG_ALREADY_SET "argument %c že ima nastavljeno vrednost (mogoče okoljske spremenljivke)"
-#define DC_I18N_MISSING_EP "manjka poštni naslov in/ali geslo"
+#define DC_I18N_MISSING_EP "manjka poštni naslov in/ali geslo" /**/
#define DC_I18N_LOGGED_IN "prijavljeni ste kot %s"
#define DC_I18N_LOGIN_FAILED "prijava neuspela. prijavite se v brskalniku. strežnik je odgovoril: %s"
#define DC_I18N_NOT_JOINED "preden lahko pišeš, se moraš pridružiti kanalu"
#define DC_I18N_GUILD_NOT_SET "skupina ni izbrana!"
#define DC_I18N_CHANNEL_NOT_SET "kanal ni izbran!"
#define DC_I18N_MESSAGES_GET_FAIL "pri pridobivanju sporočil je prišlo do napake"
+#define DC_I18N_JSON_ERROR_BEFORE "JSON napaka pred"
diff --git a/src/main.c b/src/main.c
index dec4c7e..ad7c174 100644
--- a/src/main.c
+++ b/src/main.c
@@ -60,7 +60,7 @@ int main(int argc, char ** argv) {
struct writefunc_string s;
init_writefunc_string(&s);
- char * data = malloc(sizeof(DC_LOGIN_FORMAT)+strlen(email)+strlen(password));
+ char * data = malloc(strlen(DC_LOGIN_FORMAT)+strlen(email)+strlen(password));
sprintf(data, DC_LOGIN_FORMAT, email, password);
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");