diff options
Diffstat (limited to 'src/api.c')
-rw-r--r-- | src/api.c | 189 |
1 files changed, 97 insertions, 92 deletions
@@ -18,9 +18,9 @@ #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, l, m, ...) (dc_push_error(e, s, l, __func__, __FILE__, __LINE__, 0##__VA_OPT__(1), m __VA_OPT__(,) __VA_ARGS__)) */ -#define DC_ERROR(e, s, l, m, ...) (fprintf(stderr, "%s()@" __FILE__ ":%d: " m "\n", __func__, __LINE__ __VA_OPT__(,) __VA_ARGS__)) -#define DC_CLIENT_ERROR(c, m, ...) DC_ERROR(c->errors, &c->errors_sizeof, c->errors_lock, m __VA_OPT__(,) __VA_ARGS__) /* yeah, that m is not a typo */ +#define DC_ERROR(c, m, ...) (dc_push_error(c, __func__, __FILE__, __LINE__, 0##__VA_OPT__(1), m __VA_OPT__(,) __VA_ARGS__)) +/* #define DC_ERROR(c, m, ...) (fprintf(stderr, "%s()@" __FILE__ ":%d: " m "\n", __func__, __LINE__ __VA_OPT__(,) __VA_ARGS__)) */ +#define DC_CLIENT_ERROR(c, m, ...) DC_ERROR(c, m __VA_OPT__(,) __VA_ARGS__) /* yeah, that m is not a typo */ #define DC_CAPI(c, body, endpoint, ...) dc_api(c, 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" @@ -105,6 +105,7 @@ struct dc_channel { _Atomic(struct dc_guild *) guild; /* nofree, nouiw */ struct dc_message ** messages; /* yesfree, nouiw */ _Atomic(size_t) messages_sizeof; /* nouiw */ + int slowmode; /* number of seconds to wait in case of slowmode, HANDLED BY THE USER INTERFACE!! */ }; void dc_channel_free (struct dc_channel * ch) { /* noui, noapi, nolock - only called by dc_guild_free */ free(ch->name); ch->name = NULL; @@ -153,6 +154,7 @@ struct dc_client { struct dc_message ** sent_messages; /* yesfree - ui appends, api pops and moves to messages */ _Atomic(size_t) sent_messages_sizeof; pthread_rwlock_t * sent_messages_lock; + _Atomic(time_t) last_sent_message; /* for slowmode implementations */ }; struct dc_client * dc_client_init () { /* gives you a prepared dc_client */ struct dc_client * c = calloc(1, sizeof(struct dc_client)); @@ -196,11 +198,17 @@ void dc_client_free (struct dc_client * c) { /* noui, noapi, nolock - only calle DC_CFLD(c->sent_messages); free(c); } -int dc_push_error (struct dc_error ** e, _Atomic size_t * s, pthread_rwlock_t * lock, const char * c, char * f, size_t l, unsigned short int isfmt, char * m, ...) { - if (lock && pthread_rwlock_wrlock(lock)) +int dc_push_error (struct dc_client * c, const char * caller, char * f, size_t l, unsigned short int isfmt, char * m, ...) { +#define DC_PEE /* dc scalnia oziroma push error error */ c->errors[c->errors_sizeof-1] + if (!c) + return -2; + pthread_rwlock_t * lock = c->errors_lock; + if (!lock) + return -3; + if (pthread_rwlock_wrlock(lock)) return -1; /* does not report an error as that may make things even worse. I could try writing to stderr here but meh */ - e = realloc(e, sizeof(struct dc_error *)*++*s); /* note: format arguments are evaluated twice */ - e[*s-1] = malloc(sizeof(struct dc_error)); + c->errors = realloc(c->errors, sizeof(struct dc_error *)*++c->errors_sizeof); /* note: format arguments are evaluated twice */ + DC_PEE = malloc(sizeof(struct dc_error)); size_t strlenm = strlen(m); size_t va_count = parse_printf_format(m, 0, NULL); if (isfmt && va_count > 0) { @@ -208,19 +216,19 @@ int dc_push_error (struct dc_error ** e, _Atomic size_t * s, pthread_rwlock_t * 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); + DC_PEE->message = malloc(sizeof(char)*strlenm+1); + vsnprintf(DC_PEE->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; + DC_PEE->message = malloc(sizeof(char)*strlenm+1); + strcpy(DC_PEE->message, m); + } + DC_PEE->file = f; + DC_PEE->line = l; + DC_PEE->function = caller /* Caller */; + DC_PEE->time = time(NULL); + DC_PEE->reported = 0; if (lock && pthread_rwlock_unlock(lock)) return -2; return 1; @@ -240,14 +248,15 @@ cJSON * dc_api (struct dc_client * c, char * body, int isfmt, char * endpoint, . return NULL; } cJSON * json = NULL; - struct writefunc_string s, h; + struct writefunc_string * s = malloc(sizeof(struct writefunc_string)); + struct writefunc_string * h = malloc(sizeof(struct writefunc_string)); size_t va_count = parse_printf_format(endpoint, 0, NULL); char * endpoint_formatted = NULL; int retried = 0; long response_code = 0; retry: - init_writefunc_string(&s); - init_writefunc_string(&h); + init_writefunc_string(s); + init_writefunc_string(h); if (isfmt && va_count > 0 && endpoint_formatted == NULL) { va_list ap, ap2; va_start(ap, endpoint); @@ -259,34 +268,41 @@ cJSON * dc_api (struct dc_client * c, char * body, int isfmt, char * endpoint, . va_end(ap2); } curl_easy_setopt(c->curl, CURLOPT_URL, endpoint_formatted ? endpoint_formatted : endpoint); - if (!body) - curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L); curl_easy_setopt(c->curl, CURLOPT_POSTFIELDS, body); /* yes, even null is okay, it's actually a must as it makes sure curl clears old pointers and does not read freed memory. see https://github.com/curl/curl/issues/3214#issuecomment-435335974 */ - curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s); - curl_easy_setopt(c->curl, CURLOPT_HEADERDATA, &h); + if (!body) + curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L); /* this must be done after postfields */ + curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, s); + curl_easy_setopt(c->curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(c->curl, CURLOPT_HEADERDATA, h); + curl_easy_setopt(c->curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(c->curl, CURLOPT_HTTPHEADER, c->curl_headers); + curl_easy_setopt(c->curl, CURLOPT_TIMEOUT, 20L); /* 20 second timeout */ curl_easy_setopt(c->curl, CURLOPT_HEADERFUNCTION, writefunc); + curl_easy_setopt(c->curl, CURLOPT_WRITEFUNCTION, writefunc); if (curl_easy_perform(c->curl) != CURLE_OK) { DC_CLIENT_ERROR(c, "curl_easy_perform(curl) != CURLE_OK"); goto rc; } else { curl_easy_getinfo(c->curl, CURLINFO_RESPONSE_CODE, &response_code); } - fprintf(netreq, "%s\n%s\n%s\n%s====================================\n", endpoint_formatted ? endpoint_formatted : endpoint, body ? body : "GET", h.ptr, s.ptr); + fprintf(netreq, "%s\n%s\n%s\n%s====================================\n", endpoint_formatted ? endpoint_formatted : endpoint, body ? body : "GET", h->ptr, s->ptr); fflush(netreq); - char * cp = strstr(h.ptr, "\nx-ratelimit-reset-after: "); + char * cp = strstr(h->ptr, "\nx-ratelimit-reset-after: "); if (cp == NULL) goto norlheaders; cp += strlen("\nx-ratelimit-reset-after: "); double retry_after = strtod(cp, NULL); - cp = strstr(h.ptr, "\nx-ratelimit-remaining: "); + cp = strstr(h->ptr, "\nx-ratelimit-remaining: "); if (cp == NULL) goto norlheaders; cp += strlen("\nx-ratelimit-remaining: "); if (cp[0] >= '0' && cp[0] <= '9' && atoi(cp) == 0) { /* X-RateLimit-Remaining: 0 */ DC_CLIENT_ERROR(c, DC_I18N_HITRL " %lfs. endpoint = %s", retry_after, endpoint_formatted ? endpoint_formatted : endpoint); sleep(ceil(retry_after)+1); /* TODO: prevent hanging entire thread just for this */ - free(s.ptr); s.ptr = NULL; - free(h.ptr); h.ptr = NULL; + free(s->ptr); s->ptr = NULL; + free(h->ptr); h->ptr = NULL; + free(s); s = NULL; + free(h); h = NULL; cJSON_Delete(json); json = NULL; if (retried++) @@ -294,26 +310,27 @@ cJSON * dc_api (struct dc_client * c, char * body, int isfmt, char * endpoint, . goto retry; } norlheaders: - json = cJSON_Parse(s.ptr); + 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 ": %.21s s.ptr = %s", error_ptr, s.ptr); + DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %.21s s->ptr = %s", error_ptr, s->ptr); } else { - DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ". s.ptr = %s", s.ptr ? s.ptr : "NULL"); + DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ". s->ptr = %s", s->ptr ? s->ptr : "NULL"); } goto rc; } rc: curl_easy_setopt(c->curl, CURLOPT_HEADERFUNCTION, NULL); /* for usages that don't use headerfunction */ free(endpoint_formatted); - free(s.ptr); s.ptr = NULL; - free(h.ptr); h.ptr = NULL; + free(s->ptr); s->ptr = NULL; + free(h->ptr); h->ptr = NULL; + free(s); s = NULL; + free(h); h = NULL; return json; } int dc_login (struct dc_client * c) { /* noui */ int rs = 1; - struct writefunc_string s; char * data = NULL; cJSON * json = NULL; if (!c) @@ -328,34 +345,15 @@ int dc_login (struct dc_client * c) { /* noui */ DC_CLIENT_ERROR(c, "curl_easy_init() " DC_I18N_FAILED); return -3; } - init_writefunc_string(&s); - data = malloc(snprintf(data, 0, DC_LOGIN_FORMAT, c->email, c->password)+1); + data = malloc(snprintf(NULL, 0, DC_LOGIN_FORMAT, c->email, c->password)+1); sprintf(data, DC_LOGIN_FORMAT, c->email, c->password); - CURLcode res; - curl_slist_free_all(c->curl_headers); + /* curl_slist_free_all(c->curl_headers); */ 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); + json = DC_CAPI(c, data, DC_API_PREFIX "auth/login"); 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; + DC_CLIENT_ERROR(c, "!DC_CAPI auth/login, data = %s", data); + rs = -4; goto rc; } cJSON * token = cJSON_GetObjectItem(json, "token"); @@ -371,29 +369,12 @@ int dc_login (struct dc_client * c) { /* noui */ strcpy(data, "Authorization: "); strcat(data, c->authorization); if (DC_CUE(c, c->authorization_lock)) {rs = -8; goto rc;} - 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_POSTFIELDS, NULL); - 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 = -9; - goto rc; - } cJSON_Delete(json); - json = cJSON_Parse(s.ptr); + json = DC_CAPI(c, NULL, DC_API_PREFIX "users/@me"); 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 = -10; + DC_CLIENT_ERROR(c, "!DC_CAPI users/@me"); + rs = -7; goto rc; } token = cJSON_GetObjectItem(json, "username"); @@ -409,7 +390,6 @@ int dc_login (struct dc_client * c) { /* noui */ if (DC_CUE(c, c->username_lock)) {rs = -12; goto rc;} c->discriminator = strtol(token2->valuestring, NULL, 10); rc: - free(s.ptr); s.ptr = NULL; free(data); data = NULL; cJSON_Delete(json); return rs; @@ -441,6 +421,7 @@ int dc_fetch_guilds (struct dc_client * c) { if(DC_CWLE(c, c->guilds_lock)) {rs = -5; goto rc;} cJSON * guild = NULL; cJSON_ArrayForEach(guild, json) { + int skip = 0; value = cJSON_GetStringValue(cJSON_GetObjectItem(guild, "name")); value2 = cJSON_GetStringValue(cJSON_GetObjectItem(guild, "id")); if (!value || !value2) { @@ -451,8 +432,11 @@ int dc_fetch_guilds (struct dc_client * c) { } unsigned long long int idull = strtoull(value2, NULL, 10); for (int i = 0; i < c->guilds_sizeof; i++) - if (idull == c->guilds[i]->id) - continue; /* remove duplicates */ + if (idull == c->guilds[i]->id) { + skip = 1; + break; /* remove duplicates */ + } + if (skip) continue; c->guilds = realloc(c->guilds, sizeof(struct dc_guild *)*++c->guilds_sizeof); c->guilds[c->guilds_sizeof-1] = malloc(sizeof(struct dc_guild)); c->guilds[c->guilds_sizeof-1]->name = malloc(strlen(value)+1); @@ -496,13 +480,22 @@ int dc_fetch_channels (struct dc_guild * g) { goto rc; } cJSON * channel = NULL; + cJSON * perms = NULL; + cJSON * perm = NULL; /* we lock all client guilds when doing stuff with channels */ if (DC_CWLE(c, c->guilds_lock)) {rs = -7; goto rc;} cJSON_ArrayForEach(channel, json) { + int skip = 0; + if (cJSON_IsArray(perms = cJSON_GetObjectItem(channel, "permission_overwrites"))); /* not supported */ + cJSON_ArrayForEach(perm, perms) { + skip++; + } + if (skip) continue; char * topic = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "topic")); char * name = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "name")); char * id = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "id")); double type = cJSON_GetNumberValue(cJSON_GetObjectItem(channel, "type")); + double slowmode = cJSON_GetNumberValue(cJSON_GetObjectItem(channel, "rate_limit_per_user")); if (!id || !name || type == NAN) { DC_CLIENT_ERROR(c, "!id || !name || type == NAN"); continue; @@ -513,8 +506,11 @@ int dc_fetch_channels (struct dc_guild * g) { topic = ""; unsigned long long int idull = strtoull(id, NULL, 10); for (int i = 0; i < g->channels_sizeof; i++) - if (idull == g->channels[i]->id) - continue; /* remove duplicates */ + if (idull == g->channels[i]->id) { + skip++; + break; + } + if (skip) continue; g->channels = realloc(g->channels, sizeof(struct dc_channel *)*++g->channels_sizeof); g->channels[g->channels_sizeof-1] = malloc(sizeof(struct dc_channel)); g->channels[g->channels_sizeof-1]->name = malloc(strlen(name)+1); @@ -525,6 +521,7 @@ int dc_fetch_channels (struct dc_guild * g) { 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; + g->channels[g->channels_sizeof-1]->slowmode = slowmode != NAN ? slowmode : 0; } if (DC_CUE(c, c->guilds_lock)) {rs = -8; goto rc;} rc: @@ -583,6 +580,7 @@ int dc_send_message (struct dc_message * m) { /* nolock - once message is append rc: free(body); body = NULL; cJSON_Delete(json); json = NULL; + c->last_sent_message = time(NULL); return rs; } int dc_fetch_messages (struct dc_channel * ch) { @@ -616,6 +614,7 @@ int dc_fetch_messages (struct dc_channel * ch) { if (DC_CWLE(c, c->guilds_lock)) {rs = -7; goto rc;} /* we lock all guilds of a client when writing messages */ cJSON * message = NULL; cJSON_ArrayForEach(message, json) { + int skip = 0; char * timestamp = cJSON_GetStringValue(cJSON_GetObjectItem(message, "timestamp")); char * content = cJSON_GetStringValue(cJSON_GetObjectItem(message, "content")); char * id = cJSON_GetStringValue(cJSON_GetObjectItem(message, "id")); @@ -637,11 +636,15 @@ int dc_fetch_messages (struct dc_channel * ch) { } 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 */ + if (idull == ch->messages[i]->id) { + skip++; + break; /* remove duplicates */ + } + if (skip) continue; ch->messages = realloc(ch->messages, sizeof(struct dc_message *)*++ch->messages_sizeof); #define DC_FMTM /* fetch messages this message */ ch->messages[ch->messages_sizeof-1] - DC_FMTM = malloc(sizeof(struct dc_message)); + /* DC_CLIENT_ERROR(c, "recvd msg %llu", idull); */ /* remember: continue in a nested forloop is not useful in some cases (: */ + DC_FMTM = calloc(1, sizeof(struct dc_message)); DC_FMTM->time = mktime(&tm); DC_FMTM->content = malloc(strlen(content)+1); strcpy(DC_FMTM->content, content); @@ -651,7 +654,7 @@ int dc_fetch_messages (struct dc_channel * ch) { 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 */ + 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 */ if (DC_CUE(c, c->guilds_lock)) {rs = -8; goto rc;} rc: cJSON_Delete(json); json = NULL; @@ -670,6 +673,7 @@ struct dc_thread_control { }; int dc_api_thread (struct dc_thread_control * t) { /* updates messages and sends messages when they are in the outbox */ while (!t->power_api) usleep(250000); /* so as to not make the switcher go bankrupt */ + curl_global_init(CURL_GLOBAL_ALL); /* if (pthread_rwlock_wrlock(t->clients_lock)) return -1; */ /* clients are not locked yet */ for (int i = 0; i < t->clients_sizeof && t->power_api != 2; i++) @@ -683,9 +687,9 @@ int dc_api_thread (struct dc_thread_control * t) { /* updates messages and sends for (int j = 0; j < t->clients[i]->guilds_sizeof; j++) { if (!t->clients[i]->guilds[j]->channels_sizeof /*|| !(rand() % 100)*/) /* roughly every 100+inf cycles we'll update channels */ dc_fetch_channels(t->clients[i]->guilds[j]); - for (int k = 0; k < t->clients[i]->guilds[j]->channels_sizeof; k++) - if (!(rand() % 10)) /* roughly every 10 cycles we'll update messages */ - dc_fetch_messages(t->clients[i]->guilds[j]->channels[k]); + /* for (int k = 0; k < t->clients[i]->guilds[j]->channels_sizeof; k++) */ + if (!(rand() % 10) && t->clients[i]->joinedchannel) /* roughly every 10 cycles we'll update messages in the joined ch */ + dc_fetch_messages(t->clients[i]->joinedchannel); } if (DC_CWLE(t->clients[i], t->clients[i]->sent_messages_lock)) continue; if (t->clients[i]->sent_messages_sizeof > 0) { @@ -695,7 +699,7 @@ int dc_api_thread (struct dc_thread_control * t) { /* updates messages and sends msg2send->channel->messages = realloc(msg2send->channel->messages, sizeof(struct dc_message *)*++msg2send->channel->messages_sizeof); msg2send->channel->messages[msg2send->channel->messages_sizeof-1] = msg2send; DC_CUE(t->clients[i], t->clients[i]->guilds_lock); */ /* we will no longer do this, let the thread fetch the msg */ - for (int j = 0; j <= t->clients[i]->sent_messages_sizeof; j++) /* shift, we removed one from the start */ + for (int j = 0; j < t->clients[i]->sent_messages_sizeof-1; j++) /* shift, we removed one from the start */ t->clients[i]->sent_messages[j] = t->clients[i]->sent_messages[j+1]; t->clients[i]->sent_messages_sizeof--; } @@ -705,6 +709,7 @@ int dc_api_thread (struct dc_thread_control * t) { /* updates messages and sends } usleep(250000); } + curl_global_cleanup(); /* if (pthread_rwlock_unlock(t->clients_lock)) return -2; */ return 1; |